diff --git a/.htaccess b/.htaccess index ebb28b0887..11520d743d 100644 --- a/.htaccess +++ b/.htaccess @@ -1,3 +1,4 @@ +ErrorDocument 403 /core/templates/403.php ErrorDocument 404 /core/templates/404.php php_value upload_max_filesize 512M diff --git a/3rdparty/Archive/Tar.php b/3rdparty/Archive/Tar.php new file mode 100644 index 0000000000..d8eae851bd --- /dev/null +++ b/3rdparty/Archive/Tar.php @@ -0,0 +1,1954 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category File_Formats + * @package Archive_Tar + * @author Vincent Blavet + * @copyright 1997-2010 The Authors + * @license http://www.opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Tar.php 323476 2012-02-24 15:27:26Z mrook $ + * @link http://pear.php.net/package/Archive_Tar + */ + +require_once 'PEAR.php'; + +define('ARCHIVE_TAR_ATT_SEPARATOR', 90001); +define('ARCHIVE_TAR_END_BLOCK', pack("a512", '')); + +/** +* Creates a (compressed) Tar archive +* +* @package Archive_Tar +* @author Vincent Blavet +* @license http://www.opensource.org/licenses/bsd-license.php New BSD License +* @version $Revision: 323476 $ +*/ +class Archive_Tar extends PEAR +{ + /** + * @var string Name of the Tar + */ + var $_tarname=''; + + /** + * @var boolean if true, the Tar file will be gzipped + */ + var $_compress=false; + + /** + * @var string Type of compression : 'none', 'gz' or 'bz2' + */ + var $_compress_type='none'; + + /** + * @var string Explode separator + */ + var $_separator=' '; + + /** + * @var file descriptor + */ + var $_file=0; + + /** + * @var string Local Tar name of a remote Tar (http:// or ftp://) + */ + var $_temp_tarname=''; + + /** + * @var string regular expression for ignoring files or directories + */ + var $_ignore_regexp=''; + + /** + * @var object PEAR_Error object + */ + var $error_object=null; + + // {{{ constructor + /** + * Archive_Tar Class constructor. This flavour of the constructor only + * declare a new Archive_Tar object, identifying it by the name of the + * tar file. + * If the compress argument is set the tar will be read or created as a + * gzip or bz2 compressed TAR file. + * + * @param string $p_tarname The name of the tar archive to create + * @param string $p_compress can be null, 'gz' or 'bz2'. This + * parameter indicates if gzip or bz2 compression + * is required. For compatibility reason the + * boolean value 'true' means 'gz'. + * + * @access public + */ + function Archive_Tar($p_tarname, $p_compress = null) + { + $this->PEAR(); + $this->_compress = false; + $this->_compress_type = 'none'; + if (($p_compress === null) || ($p_compress == '')) { + if (@file_exists($p_tarname)) { + if ($fp = @fopen($p_tarname, "rb")) { + // look for gzip magic cookie + $data = fread($fp, 2); + fclose($fp); + if ($data == "\37\213") { + $this->_compress = true; + $this->_compress_type = 'gz'; + // No sure it's enought for a magic code .... + } elseif ($data == "BZ") { + $this->_compress = true; + $this->_compress_type = 'bz2'; + } + } + } else { + // probably a remote file or some file accessible + // through a stream interface + if (substr($p_tarname, -2) == 'gz') { + $this->_compress = true; + $this->_compress_type = 'gz'; + } elseif ((substr($p_tarname, -3) == 'bz2') || + (substr($p_tarname, -2) == 'bz')) { + $this->_compress = true; + $this->_compress_type = 'bz2'; + } + } + } else { + if (($p_compress === true) || ($p_compress == 'gz')) { + $this->_compress = true; + $this->_compress_type = 'gz'; + } else if ($p_compress == 'bz2') { + $this->_compress = true; + $this->_compress_type = 'bz2'; + } else { + $this->_error("Unsupported compression type '$p_compress'\n". + "Supported types are 'gz' and 'bz2'.\n"); + return false; + } + } + $this->_tarname = $p_tarname; + if ($this->_compress) { // assert zlib or bz2 extension support + if ($this->_compress_type == 'gz') + $extname = 'zlib'; + else if ($this->_compress_type == 'bz2') + $extname = 'bz2'; + + if (!extension_loaded($extname)) { + PEAR::loadExtension($extname); + } + if (!extension_loaded($extname)) { + $this->_error("The extension '$extname' couldn't be found.\n". + "Please make sure your version of PHP was built ". + "with '$extname' support.\n"); + return false; + } + } + } + // }}} + + // {{{ destructor + function _Archive_Tar() + { + $this->_close(); + // ----- Look for a local copy to delete + if ($this->_temp_tarname != '') + @unlink($this->_temp_tarname); + $this->_PEAR(); + } + // }}} + + // {{{ create() + /** + * This method creates the archive file and add the files / directories + * that are listed in $p_filelist. + * If a file with the same name exist and is writable, it is replaced + * by the new tar. + * The method return false and a PEAR error text. + * The $p_filelist parameter can be an array of string, each string + * representing a filename or a directory name with their path if + * needed. It can also be a single string with names separated by a + * single blank. + * For each directory added in the archive, the files and + * sub-directories are also added. + * See also createModify() method for more details. + * + * @param array $p_filelist An array of filenames and directory names, or a + * single string with names separated by a single + * blank space. + * + * @return true on success, false on error. + * @see createModify() + * @access public + */ + function create($p_filelist) + { + return $this->createModify($p_filelist, '', ''); + } + // }}} + + // {{{ add() + /** + * This method add the files / directories that are listed in $p_filelist in + * the archive. If the archive does not exist it is created. + * The method return false and a PEAR error text. + * The files and directories listed are only added at the end of the archive, + * even if a file with the same name is already archived. + * See also createModify() method for more details. + * + * @param array $p_filelist An array of filenames and directory names, or a + * single string with names separated by a single + * blank space. + * + * @return true on success, false on error. + * @see createModify() + * @access public + */ + function add($p_filelist) + { + return $this->addModify($p_filelist, '', ''); + } + // }}} + + // {{{ extract() + function extract($p_path='', $p_preserve=false) + { + return $this->extractModify($p_path, '', $p_preserve); + } + // }}} + + // {{{ listContent() + function listContent() + { + $v_list_detail = array(); + + if ($this->_openRead()) { + if (!$this->_extractList('', $v_list_detail, "list", '', '')) { + unset($v_list_detail); + $v_list_detail = 0; + } + $this->_close(); + } + + return $v_list_detail; + } + // }}} + + // {{{ createModify() + /** + * This method creates the archive file and add the files / directories + * that are listed in $p_filelist. + * If the file already exists and is writable, it is replaced by the + * new tar. It is a create and not an add. If the file exists and is + * read-only or is a directory it is not replaced. The method return + * false and a PEAR error text. + * The $p_filelist parameter can be an array of string, each string + * representing a filename or a directory name with their path if + * needed. It can also be a single string with names separated by a + * single blank. + * The path indicated in $p_remove_dir will be removed from the + * memorized path of each file / directory listed when this path + * exists. By default nothing is removed (empty path '') + * The path indicated in $p_add_dir will be added at the beginning of + * the memorized path of each file / directory listed. However it can + * be set to empty ''. The adding of a path is done after the removing + * of path. + * The path add/remove ability enables the user to prepare an archive + * for extraction in a different path than the origin files are. + * See also addModify() method for file adding properties. + * + * @param array $p_filelist An array of filenames and directory names, + * or a single string with names separated by + * a single blank space. + * @param string $p_add_dir A string which contains a path to be added + * to the memorized path of each element in + * the list. + * @param string $p_remove_dir A string which contains a path to be + * removed from the memorized path of each + * element in the list, when relevant. + * + * @return boolean true on success, false on error. + * @access public + * @see addModify() + */ + function createModify($p_filelist, $p_add_dir, $p_remove_dir='') + { + $v_result = true; + + if (!$this->_openWrite()) + return false; + + if ($p_filelist != '') { + if (is_array($p_filelist)) + $v_list = $p_filelist; + elseif (is_string($p_filelist)) + $v_list = explode($this->_separator, $p_filelist); + else { + $this->_cleanFile(); + $this->_error('Invalid file list'); + return false; + } + + $v_result = $this->_addList($v_list, $p_add_dir, $p_remove_dir); + } + + if ($v_result) { + $this->_writeFooter(); + $this->_close(); + } else + $this->_cleanFile(); + + return $v_result; + } + // }}} + + // {{{ addModify() + /** + * This method add the files / directories listed in $p_filelist at the + * end of the existing archive. If the archive does not yet exists it + * is created. + * The $p_filelist parameter can be an array of string, each string + * representing a filename or a directory name with their path if + * needed. It can also be a single string with names separated by a + * single blank. + * The path indicated in $p_remove_dir will be removed from the + * memorized path of each file / directory listed when this path + * exists. By default nothing is removed (empty path '') + * The path indicated in $p_add_dir will be added at the beginning of + * the memorized path of each file / directory listed. However it can + * be set to empty ''. The adding of a path is done after the removing + * of path. + * The path add/remove ability enables the user to prepare an archive + * for extraction in a different path than the origin files are. + * If a file/dir is already in the archive it will only be added at the + * end of the archive. There is no update of the existing archived + * file/dir. However while extracting the archive, the last file will + * replace the first one. This results in a none optimization of the + * archive size. + * If a file/dir does not exist the file/dir is ignored. However an + * error text is send to PEAR error. + * If a file/dir is not readable the file/dir is ignored. However an + * error text is send to PEAR error. + * + * @param array $p_filelist An array of filenames and directory + * names, or a single string with names + * separated by a single blank space. + * @param string $p_add_dir A string which contains a path to be + * added to the memorized path of each + * element in the list. + * @param string $p_remove_dir A string which contains a path to be + * removed from the memorized path of + * each element in the list, when + * relevant. + * + * @return true on success, false on error. + * @access public + */ + function addModify($p_filelist, $p_add_dir, $p_remove_dir='') + { + $v_result = true; + + if (!$this->_isArchive()) + $v_result = $this->createModify($p_filelist, $p_add_dir, + $p_remove_dir); + else { + if (is_array($p_filelist)) + $v_list = $p_filelist; + elseif (is_string($p_filelist)) + $v_list = explode($this->_separator, $p_filelist); + else { + $this->_error('Invalid file list'); + return false; + } + + $v_result = $this->_append($v_list, $p_add_dir, $p_remove_dir); + } + + return $v_result; + } + // }}} + + // {{{ addString() + /** + * This method add a single string as a file at the + * end of the existing archive. If the archive does not yet exists it + * is created. + * + * @param string $p_filename A string which contains the full + * filename path that will be associated + * with the string. + * @param string $p_string The content of the file added in + * the archive. + * + * @return true on success, false on error. + * @access public + */ + function addString($p_filename, $p_string) + { + $v_result = true; + + if (!$this->_isArchive()) { + if (!$this->_openWrite()) { + return false; + } + $this->_close(); + } + + if (!$this->_openAppend()) + return false; + + // Need to check the get back to the temporary file ? .... + $v_result = $this->_addString($p_filename, $p_string); + + $this->_writeFooter(); + + $this->_close(); + + return $v_result; + } + // }}} + + // {{{ extractModify() + /** + * This method extract all the content of the archive in the directory + * indicated by $p_path. When relevant the memorized path of the + * files/dir can be modified by removing the $p_remove_path path at the + * beginning of the file/dir path. + * While extracting a file, if the directory path does not exists it is + * created. + * While extracting a file, if the file already exists it is replaced + * without looking for last modification date. + * While extracting a file, if the file already exists and is write + * protected, the extraction is aborted. + * While extracting a file, if a directory with the same name already + * exists, the extraction is aborted. + * While extracting a directory, if a file with the same name already + * exists, the extraction is aborted. + * While extracting a file/directory if the destination directory exist + * and is write protected, or does not exist but can not be created, + * the extraction is aborted. + * If after extraction an extracted file does not show the correct + * stored file size, the extraction is aborted. + * When the extraction is aborted, a PEAR error text is set and false + * is returned. However the result can be a partial extraction that may + * need to be manually cleaned. + * + * @param string $p_path The path of the directory where the + * files/dir need to by extracted. + * @param string $p_remove_path Part of the memorized path that can be + * removed if present at the beginning of + * the file/dir path. + * @param boolean $p_preserve Preserve user/group ownership of files + * + * @return boolean true on success, false on error. + * @access public + * @see extractList() + */ + function extractModify($p_path, $p_remove_path, $p_preserve=false) + { + $v_result = true; + $v_list_detail = array(); + + if ($v_result = $this->_openRead()) { + $v_result = $this->_extractList($p_path, $v_list_detail, + "complete", 0, $p_remove_path, $p_preserve); + $this->_close(); + } + + return $v_result; + } + // }}} + + // {{{ extractInString() + /** + * This method extract from the archive one file identified by $p_filename. + * The return value is a string with the file content, or NULL on error. + * + * @param string $p_filename The path of the file to extract in a string. + * + * @return a string with the file content or NULL. + * @access public + */ + function extractInString($p_filename) + { + if ($this->_openRead()) { + $v_result = $this->_extractInString($p_filename); + $this->_close(); + } else { + $v_result = null; + } + + return $v_result; + } + // }}} + + // {{{ extractList() + /** + * This method extract from the archive only the files indicated in the + * $p_filelist. These files are extracted in the current directory or + * in the directory indicated by the optional $p_path parameter. + * If indicated the $p_remove_path can be used in the same way as it is + * used in extractModify() method. + * + * @param array $p_filelist An array of filenames and directory names, + * or a single string with names separated + * by a single blank space. + * @param string $p_path The path of the directory where the + * files/dir need to by extracted. + * @param string $p_remove_path Part of the memorized path that can be + * removed if present at the beginning of + * the file/dir path. + * @param boolean $p_preserve Preserve user/group ownership of files + * + * @return true on success, false on error. + * @access public + * @see extractModify() + */ + function extractList($p_filelist, $p_path='', $p_remove_path='', $p_preserve=false) + { + $v_result = true; + $v_list_detail = array(); + + if (is_array($p_filelist)) + $v_list = $p_filelist; + elseif (is_string($p_filelist)) + $v_list = explode($this->_separator, $p_filelist); + else { + $this->_error('Invalid string list'); + return false; + } + + if ($v_result = $this->_openRead()) { + $v_result = $this->_extractList($p_path, $v_list_detail, "partial", + $v_list, $p_remove_path, $p_preserve); + $this->_close(); + } + + return $v_result; + } + // }}} + + // {{{ setAttribute() + /** + * This method set specific attributes of the archive. It uses a variable + * list of parameters, in the format attribute code + attribute values : + * $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ','); + * + * @param mixed $argv variable list of attributes and values + * + * @return true on success, false on error. + * @access public + */ + function setAttribute() + { + $v_result = true; + + // ----- Get the number of variable list of arguments + if (($v_size = func_num_args()) == 0) { + return true; + } + + // ----- Get the arguments + $v_att_list = &func_get_args(); + + // ----- Read the attributes + $i=0; + while ($i<$v_size) { + + // ----- Look for next option + switch ($v_att_list[$i]) { + // ----- Look for options that request a string value + case ARCHIVE_TAR_ATT_SEPARATOR : + // ----- Check the number of parameters + if (($i+1) >= $v_size) { + $this->_error('Invalid number of parameters for ' + .'attribute ARCHIVE_TAR_ATT_SEPARATOR'); + return false; + } + + // ----- Get the value + $this->_separator = $v_att_list[$i+1]; + $i++; + break; + + default : + $this->_error('Unknow attribute code '.$v_att_list[$i].''); + return false; + } + + // ----- Next attribute + $i++; + } + + return $v_result; + } + // }}} + + // {{{ setIgnoreRegexp() + /** + * This method sets the regular expression for ignoring files and directories + * at import, for example: + * $arch->setIgnoreRegexp("#CVS|\.svn#"); + * + * @param string $regexp regular expression defining which files or directories to ignore + * + * @access public + */ + function setIgnoreRegexp($regexp) + { + $this->_ignore_regexp = $regexp; + } + // }}} + + // {{{ setIgnoreList() + /** + * This method sets the regular expression for ignoring all files and directories + * matching the filenames in the array list at import, for example: + * $arch->setIgnoreList(array('CVS', '.svn', 'bin/tool')); + * + * @param array $list a list of file or directory names to ignore + * + * @access public + */ + function setIgnoreList($list) + { + $regexp = str_replace(array('#', '.', '^', '$'), array('\#', '\.', '\^', '\$'), $list); + $regexp = '#/'.join('$|/', $list).'#'; + $this->setIgnoreRegexp($regexp); + } + // }}} + + // {{{ _error() + function _error($p_message) + { + $this->error_object = $this->raiseError($p_message); + } + // }}} + + // {{{ _warning() + function _warning($p_message) + { + $this->error_object = $this->raiseError($p_message); + } + // }}} + + // {{{ _isArchive() + function _isArchive($p_filename=null) + { + if ($p_filename == null) { + $p_filename = $this->_tarname; + } + clearstatcache(); + return @is_file($p_filename) && !@is_link($p_filename); + } + // }}} + + // {{{ _openWrite() + function _openWrite() + { + if ($this->_compress_type == 'gz' && function_exists('gzopen')) + $this->_file = @gzopen($this->_tarname, "wb9"); + else if ($this->_compress_type == 'bz2' && function_exists('bzopen')) + $this->_file = @bzopen($this->_tarname, "w"); + else if ($this->_compress_type == 'none') + $this->_file = @fopen($this->_tarname, "wb"); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + if ($this->_file == 0) { + $this->_error('Unable to open in write mode \'' + .$this->_tarname.'\''); + return false; + } + + return true; + } + // }}} + + // {{{ _openRead() + function _openRead() + { + if (strtolower(substr($this->_tarname, 0, 7)) == 'http://') { + + // ----- Look if a local copy need to be done + if ($this->_temp_tarname == '') { + $this->_temp_tarname = uniqid('tar').'.tmp'; + if (!$v_file_from = @fopen($this->_tarname, 'rb')) { + $this->_error('Unable to open in read mode \'' + .$this->_tarname.'\''); + $this->_temp_tarname = ''; + return false; + } + if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) { + $this->_error('Unable to open in write mode \'' + .$this->_temp_tarname.'\''); + $this->_temp_tarname = ''; + return false; + } + while ($v_data = @fread($v_file_from, 1024)) + @fwrite($v_file_to, $v_data); + @fclose($v_file_from); + @fclose($v_file_to); + } + + // ----- File to open if the local copy + $v_filename = $this->_temp_tarname; + + } else + // ----- File to open if the normal Tar file + $v_filename = $this->_tarname; + + if ($this->_compress_type == 'gz') + $this->_file = @gzopen($v_filename, "rb"); + else if ($this->_compress_type == 'bz2') + $this->_file = @bzopen($v_filename, "r"); + else if ($this->_compress_type == 'none') + $this->_file = @fopen($v_filename, "rb"); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + if ($this->_file == 0) { + $this->_error('Unable to open in read mode \''.$v_filename.'\''); + return false; + } + + return true; + } + // }}} + + // {{{ _openReadWrite() + function _openReadWrite() + { + if ($this->_compress_type == 'gz') + $this->_file = @gzopen($this->_tarname, "r+b"); + else if ($this->_compress_type == 'bz2') { + $this->_error('Unable to open bz2 in read/write mode \'' + .$this->_tarname.'\' (limitation of bz2 extension)'); + return false; + } else if ($this->_compress_type == 'none') + $this->_file = @fopen($this->_tarname, "r+b"); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + if ($this->_file == 0) { + $this->_error('Unable to open in read/write mode \'' + .$this->_tarname.'\''); + return false; + } + + return true; + } + // }}} + + // {{{ _close() + function _close() + { + //if (isset($this->_file)) { + if (is_resource($this->_file)) { + if ($this->_compress_type == 'gz') + @gzclose($this->_file); + else if ($this->_compress_type == 'bz2') + @bzclose($this->_file); + else if ($this->_compress_type == 'none') + @fclose($this->_file); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + $this->_file = 0; + } + + // ----- Look if a local copy need to be erase + // Note that it might be interesting to keep the url for a time : ToDo + if ($this->_temp_tarname != '') { + @unlink($this->_temp_tarname); + $this->_temp_tarname = ''; + } + + return true; + } + // }}} + + // {{{ _cleanFile() + function _cleanFile() + { + $this->_close(); + + // ----- Look for a local copy + if ($this->_temp_tarname != '') { + // ----- Remove the local copy but not the remote tarname + @unlink($this->_temp_tarname); + $this->_temp_tarname = ''; + } else { + // ----- Remove the local tarname file + @unlink($this->_tarname); + } + $this->_tarname = ''; + + return true; + } + // }}} + + // {{{ _writeBlock() + function _writeBlock($p_binary_data, $p_len=null) + { + if (is_resource($this->_file)) { + if ($p_len === null) { + if ($this->_compress_type == 'gz') + @gzputs($this->_file, $p_binary_data); + else if ($this->_compress_type == 'bz2') + @bzwrite($this->_file, $p_binary_data); + else if ($this->_compress_type == 'none') + @fputs($this->_file, $p_binary_data); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + } else { + if ($this->_compress_type == 'gz') + @gzputs($this->_file, $p_binary_data, $p_len); + else if ($this->_compress_type == 'bz2') + @bzwrite($this->_file, $p_binary_data, $p_len); + else if ($this->_compress_type == 'none') + @fputs($this->_file, $p_binary_data, $p_len); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + } + } + return true; + } + // }}} + + // {{{ _readBlock() + function _readBlock() + { + $v_block = null; + if (is_resource($this->_file)) { + if ($this->_compress_type == 'gz') + $v_block = @gzread($this->_file, 512); + else if ($this->_compress_type == 'bz2') + $v_block = @bzread($this->_file, 512); + else if ($this->_compress_type == 'none') + $v_block = @fread($this->_file, 512); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + } + return $v_block; + } + // }}} + + // {{{ _jumpBlock() + function _jumpBlock($p_len=null) + { + if (is_resource($this->_file)) { + if ($p_len === null) + $p_len = 1; + + if ($this->_compress_type == 'gz') { + @gzseek($this->_file, gztell($this->_file)+($p_len*512)); + } + else if ($this->_compress_type == 'bz2') { + // ----- Replace missing bztell() and bzseek() + for ($i=0; $i<$p_len; $i++) + $this->_readBlock(); + } else if ($this->_compress_type == 'none') + @fseek($this->_file, $p_len*512, SEEK_CUR); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + } + return true; + } + // }}} + + // {{{ _writeFooter() + function _writeFooter() + { + if (is_resource($this->_file)) { + // ----- Write the last 0 filled block for end of archive + $v_binary_data = pack('a1024', ''); + $this->_writeBlock($v_binary_data); + } + return true; + } + // }}} + + // {{{ _addList() + function _addList($p_list, $p_add_dir, $p_remove_dir) + { + $v_result=true; + $v_header = array(); + + // ----- Remove potential windows directory separator + $p_add_dir = $this->_translateWinPath($p_add_dir); + $p_remove_dir = $this->_translateWinPath($p_remove_dir, false); + + if (!$this->_file) { + $this->_error('Invalid file descriptor'); + return false; + } + + if (sizeof($p_list) == 0) + return true; + + foreach ($p_list as $v_filename) { + if (!$v_result) { + break; + } + + // ----- Skip the current tar name + if ($v_filename == $this->_tarname) + continue; + + if ($v_filename == '') + continue; + + // ----- ignore files and directories matching the ignore regular expression + if ($this->_ignore_regexp && preg_match($this->_ignore_regexp, '/'.$v_filename)) { + $this->_warning("File '$v_filename' ignored"); + continue; + } + + if (!file_exists($v_filename) && !is_link($v_filename)) { + $this->_warning("File '$v_filename' does not exist"); + continue; + } + + // ----- Add the file or directory header + if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir)) + return false; + + if (@is_dir($v_filename) && !@is_link($v_filename)) { + if (!($p_hdir = opendir($v_filename))) { + $this->_warning("Directory '$v_filename' can not be read"); + continue; + } + while (false !== ($p_hitem = readdir($p_hdir))) { + if (($p_hitem != '.') && ($p_hitem != '..')) { + if ($v_filename != ".") + $p_temp_list[0] = $v_filename.'/'.$p_hitem; + else + $p_temp_list[0] = $p_hitem; + + $v_result = $this->_addList($p_temp_list, + $p_add_dir, + $p_remove_dir); + } + } + + unset($p_temp_list); + unset($p_hdir); + unset($p_hitem); + } + } + + return $v_result; + } + // }}} + + // {{{ _addFile() + function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir,$v_stored_filename=null) + { + if (!$this->_file) { + $this->_error('Invalid file descriptor'); + return false; + } + + if ($p_filename == '') { + $this->_error('Invalid file name'); + return false; + } + if(is_null($v_stored_filename)){ + + // ----- Calculate the stored filename + $p_filename = $this->_translateWinPath($p_filename, false); + $v_stored_filename = $p_filename; + if (strcmp($p_filename, $p_remove_dir) == 0) { + return true; + } + if ($p_remove_dir != '') { + if (substr($p_remove_dir, -1) != '/') + $p_remove_dir .= '/'; + + if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir) + $v_stored_filename = substr($p_filename, strlen($p_remove_dir)); + } + $v_stored_filename = $this->_translateWinPath($v_stored_filename); + if ($p_add_dir != '') { + if (substr($p_add_dir, -1) == '/') + $v_stored_filename = $p_add_dir.$v_stored_filename; + else + $v_stored_filename = $p_add_dir.'/'.$v_stored_filename; + } + + $v_stored_filename = $this->_pathReduction($v_stored_filename); + } + + if ($this->_isArchive($p_filename)) { + if (($v_file = @fopen($p_filename, "rb")) == 0) { + $this->_warning("Unable to open file '".$p_filename + ."' in binary read mode"); + return true; + } + + if (!$this->_writeHeader($p_filename, $v_stored_filename)) + return false; + + while (($v_buffer = fread($v_file, 512)) != '') { + $v_binary_data = pack("a512", "$v_buffer"); + $this->_writeBlock($v_binary_data); + } + + fclose($v_file); + + } else { + // ----- Only header for dir + if (!$this->_writeHeader($p_filename, $v_stored_filename)) + return false; + } + + return true; + } + // }}} + + // {{{ _addString() + function _addString($p_filename, $p_string) + { + if (!$this->_file) { + $this->_error('Invalid file descriptor'); + return false; + } + + if ($p_filename == '') { + $this->_error('Invalid file name'); + return false; + } + + // ----- Calculate the stored filename + $p_filename = $this->_translateWinPath($p_filename, false);; + + if (!$this->_writeHeaderBlock($p_filename, strlen($p_string), + time(), 384, "", 0, 0)) + return false; + + $i=0; + while (($v_buffer = substr($p_string, (($i++)*512), 512)) != '') { + $v_binary_data = pack("a512", $v_buffer); + $this->_writeBlock($v_binary_data); + } + + return true; + } + // }}} + + // {{{ _writeHeader() + function _writeHeader($p_filename, $p_stored_filename) + { + if ($p_stored_filename == '') + $p_stored_filename = $p_filename; + $v_reduce_filename = $this->_pathReduction($p_stored_filename); + + if (strlen($v_reduce_filename) > 99) { + if (!$this->_writeLongHeader($v_reduce_filename)) + return false; + } + + $v_info = lstat($p_filename); + $v_uid = sprintf("%07s", DecOct($v_info[4])); + $v_gid = sprintf("%07s", DecOct($v_info[5])); + $v_perms = sprintf("%07s", DecOct($v_info['mode'] & 000777)); + + $v_mtime = sprintf("%011s", DecOct($v_info['mtime'])); + + $v_linkname = ''; + + if (@is_link($p_filename)) { + $v_typeflag = '2'; + $v_linkname = readlink($p_filename); + $v_size = sprintf("%011s", DecOct(0)); + } elseif (@is_dir($p_filename)) { + $v_typeflag = "5"; + $v_size = sprintf("%011s", DecOct(0)); + } else { + $v_typeflag = '0'; + clearstatcache(); + $v_size = sprintf("%011s", DecOct($v_info['size'])); + } + + $v_magic = 'ustar '; + + $v_version = ' '; + + if (function_exists('posix_getpwuid')) + { + $userinfo = posix_getpwuid($v_info[4]); + $groupinfo = posix_getgrgid($v_info[5]); + + $v_uname = $userinfo['name']; + $v_gname = $groupinfo['name']; + } + else + { + $v_uname = ''; + $v_gname = ''; + } + + $v_devmajor = ''; + + $v_devminor = ''; + + $v_prefix = ''; + + $v_binary_data_first = pack("a100a8a8a8a12a12", + $v_reduce_filename, $v_perms, $v_uid, + $v_gid, $v_size, $v_mtime); + $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", + $v_typeflag, $v_linkname, $v_magic, + $v_version, $v_uname, $v_gname, + $v_devmajor, $v_devminor, $v_prefix, ''); + + // ----- Calculate the checksum + $v_checksum = 0; + // ..... First part of the header + for ($i=0; $i<148; $i++) + $v_checksum += ord(substr($v_binary_data_first,$i,1)); + // ..... Ignore the checksum value and replace it by ' ' (space) + for ($i=148; $i<156; $i++) + $v_checksum += ord(' '); + // ..... Last part of the header + for ($i=156, $j=0; $i<512; $i++, $j++) + $v_checksum += ord(substr($v_binary_data_last,$j,1)); + + // ----- Write the first 148 bytes of the header in the archive + $this->_writeBlock($v_binary_data_first, 148); + + // ----- Write the calculated checksum + $v_checksum = sprintf("%06s ", DecOct($v_checksum)); + $v_binary_data = pack("a8", $v_checksum); + $this->_writeBlock($v_binary_data, 8); + + // ----- Write the last 356 bytes of the header in the archive + $this->_writeBlock($v_binary_data_last, 356); + + return true; + } + // }}} + + // {{{ _writeHeaderBlock() + function _writeHeaderBlock($p_filename, $p_size, $p_mtime=0, $p_perms=0, + $p_type='', $p_uid=0, $p_gid=0) + { + $p_filename = $this->_pathReduction($p_filename); + + if (strlen($p_filename) > 99) { + if (!$this->_writeLongHeader($p_filename)) + return false; + } + + if ($p_type == "5") { + $v_size = sprintf("%011s", DecOct(0)); + } else { + $v_size = sprintf("%011s", DecOct($p_size)); + } + + $v_uid = sprintf("%07s", DecOct($p_uid)); + $v_gid = sprintf("%07s", DecOct($p_gid)); + $v_perms = sprintf("%07s", DecOct($p_perms & 000777)); + + $v_mtime = sprintf("%11s", DecOct($p_mtime)); + + $v_linkname = ''; + + $v_magic = 'ustar '; + + $v_version = ' '; + + if (function_exists('posix_getpwuid')) + { + $userinfo = posix_getpwuid($p_uid); + $groupinfo = posix_getgrgid($p_gid); + + $v_uname = $userinfo['name']; + $v_gname = $groupinfo['name']; + } + else + { + $v_uname = ''; + $v_gname = ''; + } + + $v_devmajor = ''; + + $v_devminor = ''; + + $v_prefix = ''; + + $v_binary_data_first = pack("a100a8a8a8a12A12", + $p_filename, $v_perms, $v_uid, $v_gid, + $v_size, $v_mtime); + $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", + $p_type, $v_linkname, $v_magic, + $v_version, $v_uname, $v_gname, + $v_devmajor, $v_devminor, $v_prefix, ''); + + // ----- Calculate the checksum + $v_checksum = 0; + // ..... First part of the header + for ($i=0; $i<148; $i++) + $v_checksum += ord(substr($v_binary_data_first,$i,1)); + // ..... Ignore the checksum value and replace it by ' ' (space) + for ($i=148; $i<156; $i++) + $v_checksum += ord(' '); + // ..... Last part of the header + for ($i=156, $j=0; $i<512; $i++, $j++) + $v_checksum += ord(substr($v_binary_data_last,$j,1)); + + // ----- Write the first 148 bytes of the header in the archive + $this->_writeBlock($v_binary_data_first, 148); + + // ----- Write the calculated checksum + $v_checksum = sprintf("%06s ", DecOct($v_checksum)); + $v_binary_data = pack("a8", $v_checksum); + $this->_writeBlock($v_binary_data, 8); + + // ----- Write the last 356 bytes of the header in the archive + $this->_writeBlock($v_binary_data_last, 356); + + return true; + } + // }}} + + // {{{ _writeLongHeader() + function _writeLongHeader($p_filename) + { + $v_size = sprintf("%11s ", DecOct(strlen($p_filename))); + + $v_typeflag = 'L'; + + $v_linkname = ''; + + $v_magic = ''; + + $v_version = ''; + + $v_uname = ''; + + $v_gname = ''; + + $v_devmajor = ''; + + $v_devminor = ''; + + $v_prefix = ''; + + $v_binary_data_first = pack("a100a8a8a8a12a12", + '././@LongLink', 0, 0, 0, $v_size, 0); + $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", + $v_typeflag, $v_linkname, $v_magic, + $v_version, $v_uname, $v_gname, + $v_devmajor, $v_devminor, $v_prefix, ''); + + // ----- Calculate the checksum + $v_checksum = 0; + // ..... First part of the header + for ($i=0; $i<148; $i++) + $v_checksum += ord(substr($v_binary_data_first,$i,1)); + // ..... Ignore the checksum value and replace it by ' ' (space) + for ($i=148; $i<156; $i++) + $v_checksum += ord(' '); + // ..... Last part of the header + for ($i=156, $j=0; $i<512; $i++, $j++) + $v_checksum += ord(substr($v_binary_data_last,$j,1)); + + // ----- Write the first 148 bytes of the header in the archive + $this->_writeBlock($v_binary_data_first, 148); + + // ----- Write the calculated checksum + $v_checksum = sprintf("%06s ", DecOct($v_checksum)); + $v_binary_data = pack("a8", $v_checksum); + $this->_writeBlock($v_binary_data, 8); + + // ----- Write the last 356 bytes of the header in the archive + $this->_writeBlock($v_binary_data_last, 356); + + // ----- Write the filename as content of the block + $i=0; + while (($v_buffer = substr($p_filename, (($i++)*512), 512)) != '') { + $v_binary_data = pack("a512", "$v_buffer"); + $this->_writeBlock($v_binary_data); + } + + return true; + } + // }}} + + // {{{ _readHeader() + function _readHeader($v_binary_data, &$v_header) + { + if (strlen($v_binary_data)==0) { + $v_header['filename'] = ''; + return true; + } + + if (strlen($v_binary_data) != 512) { + $v_header['filename'] = ''; + $this->_error('Invalid block size : '.strlen($v_binary_data)); + return false; + } + + if (!is_array($v_header)) { + $v_header = array(); + } + // ----- Calculate the checksum + $v_checksum = 0; + // ..... First part of the header + for ($i=0; $i<148; $i++) + $v_checksum+=ord(substr($v_binary_data,$i,1)); + // ..... Ignore the checksum value and replace it by ' ' (space) + for ($i=148; $i<156; $i++) + $v_checksum += ord(' '); + // ..... Last part of the header + for ($i=156; $i<512; $i++) + $v_checksum+=ord(substr($v_binary_data,$i,1)); + + $v_data = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/" . + "a8checksum/a1typeflag/a100link/a6magic/a2version/" . + "a32uname/a32gname/a8devmajor/a8devminor/a131prefix", + $v_binary_data); + + if (strlen($v_data["prefix"]) > 0) { + $v_data["filename"] = "$v_data[prefix]/$v_data[filename]"; + } + + // ----- Extract the checksum + $v_header['checksum'] = OctDec(trim($v_data['checksum'])); + if ($v_header['checksum'] != $v_checksum) { + $v_header['filename'] = ''; + + // ----- Look for last block (empty block) + if (($v_checksum == 256) && ($v_header['checksum'] == 0)) + return true; + + $this->_error('Invalid checksum for file "'.$v_data['filename'] + .'" : '.$v_checksum.' calculated, ' + .$v_header['checksum'].' expected'); + return false; + } + + // ----- Extract the properties + $v_header['filename'] = $v_data['filename']; + if ($this->_maliciousFilename($v_header['filename'])) { + $this->_error('Malicious .tar detected, file "' . $v_header['filename'] . + '" will not install in desired directory tree'); + return false; + } + $v_header['mode'] = OctDec(trim($v_data['mode'])); + $v_header['uid'] = OctDec(trim($v_data['uid'])); + $v_header['gid'] = OctDec(trim($v_data['gid'])); + $v_header['size'] = OctDec(trim($v_data['size'])); + $v_header['mtime'] = OctDec(trim($v_data['mtime'])); + if (($v_header['typeflag'] = $v_data['typeflag']) == "5") { + $v_header['size'] = 0; + } + $v_header['link'] = trim($v_data['link']); + /* ----- All these fields are removed form the header because + they do not carry interesting info + $v_header[magic] = trim($v_data[magic]); + $v_header[version] = trim($v_data[version]); + $v_header[uname] = trim($v_data[uname]); + $v_header[gname] = trim($v_data[gname]); + $v_header[devmajor] = trim($v_data[devmajor]); + $v_header[devminor] = trim($v_data[devminor]); + */ + + return true; + } + // }}} + + // {{{ _maliciousFilename() + /** + * Detect and report a malicious file name + * + * @param string $file + * + * @return bool + * @access private + */ + function _maliciousFilename($file) + { + if (strpos($file, '/../') !== false) { + return true; + } + if (strpos($file, '../') === 0) { + return true; + } + return false; + } + // }}} + + // {{{ _readLongHeader() + function _readLongHeader(&$v_header) + { + $v_filename = ''; + $n = floor($v_header['size']/512); + for ($i=0; $i<$n; $i++) { + $v_content = $this->_readBlock(); + $v_filename .= $v_content; + } + if (($v_header['size'] % 512) != 0) { + $v_content = $this->_readBlock(); + $v_filename .= trim($v_content); + } + + // ----- Read the next header + $v_binary_data = $this->_readBlock(); + + if (!$this->_readHeader($v_binary_data, $v_header)) + return false; + + $v_filename = trim($v_filename); + $v_header['filename'] = $v_filename; + if ($this->_maliciousFilename($v_filename)) { + $this->_error('Malicious .tar detected, file "' . $v_filename . + '" will not install in desired directory tree'); + return false; + } + + return true; + } + // }}} + + // {{{ _extractInString() + /** + * This method extract from the archive one file identified by $p_filename. + * The return value is a string with the file content, or null on error. + * + * @param string $p_filename The path of the file to extract in a string. + * + * @return a string with the file content or null. + * @access private + */ + function _extractInString($p_filename) + { + $v_result_str = ""; + + While (strlen($v_binary_data = $this->_readBlock()) != 0) + { + if (!$this->_readHeader($v_binary_data, $v_header)) + return null; + + if ($v_header['filename'] == '') + continue; + + // ----- Look for long filename + if ($v_header['typeflag'] == 'L') { + if (!$this->_readLongHeader($v_header)) + return null; + } + + if ($v_header['filename'] == $p_filename) { + if ($v_header['typeflag'] == "5") { + $this->_error('Unable to extract in string a directory ' + .'entry {'.$v_header['filename'].'}'); + return null; + } else { + $n = floor($v_header['size']/512); + for ($i=0; $i<$n; $i++) { + $v_result_str .= $this->_readBlock(); + } + if (($v_header['size'] % 512) != 0) { + $v_content = $this->_readBlock(); + $v_result_str .= substr($v_content, 0, + ($v_header['size'] % 512)); + } + return $v_result_str; + } + } else { + $this->_jumpBlock(ceil(($v_header['size']/512))); + } + } + + return null; + } + // }}} + + // {{{ _extractList() + function _extractList($p_path, &$p_list_detail, $p_mode, + $p_file_list, $p_remove_path, $p_preserve=false) + { + $v_result=true; + $v_nb = 0; + $v_extract_all = true; + $v_listing = false; + + $p_path = $this->_translateWinPath($p_path, false); + if ($p_path == '' || (substr($p_path, 0, 1) != '/' + && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':'))) { + $p_path = "./".$p_path; + } + $p_remove_path = $this->_translateWinPath($p_remove_path); + + // ----- Look for path to remove format (should end by /) + if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/')) + $p_remove_path .= '/'; + $p_remove_path_size = strlen($p_remove_path); + + switch ($p_mode) { + case "complete" : + $v_extract_all = true; + $v_listing = false; + break; + case "partial" : + $v_extract_all = false; + $v_listing = false; + break; + case "list" : + $v_extract_all = false; + $v_listing = true; + break; + default : + $this->_error('Invalid extract mode ('.$p_mode.')'); + return false; + } + + clearstatcache(); + + while (strlen($v_binary_data = $this->_readBlock()) != 0) + { + $v_extract_file = FALSE; + $v_extraction_stopped = 0; + + if (!$this->_readHeader($v_binary_data, $v_header)) + return false; + + if ($v_header['filename'] == '') { + continue; + } + + // ----- Look for long filename + if ($v_header['typeflag'] == 'L') { + if (!$this->_readLongHeader($v_header)) + return false; + } + + if ((!$v_extract_all) && (is_array($p_file_list))) { + // ----- By default no unzip if the file is not found + $v_extract_file = false; + + for ($i=0; $i strlen($p_file_list[$i])) + && (substr($v_header['filename'], 0, strlen($p_file_list[$i])) + == $p_file_list[$i])) { + $v_extract_file = true; + break; + } + } + + // ----- It is a file, so compare the file names + elseif ($p_file_list[$i] == $v_header['filename']) { + $v_extract_file = true; + break; + } + } + } else { + $v_extract_file = true; + } + + // ----- Look if this file need to be extracted + if (($v_extract_file) && (!$v_listing)) + { + if (($p_remove_path != '') + && (substr($v_header['filename'], 0, $p_remove_path_size) + == $p_remove_path)) + $v_header['filename'] = substr($v_header['filename'], + $p_remove_path_size); + if (($p_path != './') && ($p_path != '/')) { + while (substr($p_path, -1) == '/') + $p_path = substr($p_path, 0, strlen($p_path)-1); + + if (substr($v_header['filename'], 0, 1) == '/') + $v_header['filename'] = $p_path.$v_header['filename']; + else + $v_header['filename'] = $p_path.'/'.$v_header['filename']; + } + if (file_exists($v_header['filename'])) { + if ( (@is_dir($v_header['filename'])) + && ($v_header['typeflag'] == '')) { + $this->_error('File '.$v_header['filename'] + .' already exists as a directory'); + return false; + } + if ( ($this->_isArchive($v_header['filename'])) + && ($v_header['typeflag'] == "5")) { + $this->_error('Directory '.$v_header['filename'] + .' already exists as a file'); + return false; + } + if (!is_writeable($v_header['filename'])) { + $this->_error('File '.$v_header['filename'] + .' already exists and is write protected'); + return false; + } + if (filemtime($v_header['filename']) > $v_header['mtime']) { + // To be completed : An error or silent no replace ? + } + } + + // ----- Check the directory availability and create it if necessary + elseif (($v_result + = $this->_dirCheck(($v_header['typeflag'] == "5" + ?$v_header['filename'] + :dirname($v_header['filename'])))) != 1) { + $this->_error('Unable to create path for '.$v_header['filename']); + return false; + } + + if ($v_extract_file) { + if ($v_header['typeflag'] == "5") { + if (!@file_exists($v_header['filename'])) { + if (!@mkdir($v_header['filename'], 0777)) { + $this->_error('Unable to create directory {' + .$v_header['filename'].'}'); + return false; + } + } + } elseif ($v_header['typeflag'] == "2") { + if (@file_exists($v_header['filename'])) { + @unlink($v_header['filename']); + } + if (!@symlink($v_header['link'], $v_header['filename'])) { + $this->_error('Unable to extract symbolic link {' + .$v_header['filename'].'}'); + return false; + } + } else { + if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) { + $this->_error('Error while opening {'.$v_header['filename'] + .'} in write binary mode'); + return false; + } else { + $n = floor($v_header['size']/512); + for ($i=0; $i<$n; $i++) { + $v_content = $this->_readBlock(); + fwrite($v_dest_file, $v_content, 512); + } + if (($v_header['size'] % 512) != 0) { + $v_content = $this->_readBlock(); + fwrite($v_dest_file, $v_content, ($v_header['size'] % 512)); + } + + @fclose($v_dest_file); + + if ($p_preserve) { + @chown($v_header['filename'], $v_header['uid']); + @chgrp($v_header['filename'], $v_header['gid']); + } + + // ----- Change the file mode, mtime + @touch($v_header['filename'], $v_header['mtime']); + if ($v_header['mode'] & 0111) { + // make file executable, obey umask + $mode = fileperms($v_header['filename']) | (~umask() & 0111); + @chmod($v_header['filename'], $mode); + } + } + + // ----- Check the file size + clearstatcache(); + if (!is_file($v_header['filename'])) { + $this->_error('Extracted file '.$v_header['filename'] + .'does not exist. Archive may be corrupted.'); + return false; + } + + $filesize = filesize($v_header['filename']); + if ($filesize != $v_header['size']) { + $this->_error('Extracted file '.$v_header['filename'] + .' does not have the correct file size \'' + .$filesize + .'\' ('.$v_header['size'] + .' expected). Archive may be corrupted.'); + return false; + } + } + } else { + $this->_jumpBlock(ceil(($v_header['size']/512))); + } + } else { + $this->_jumpBlock(ceil(($v_header['size']/512))); + } + + /* TBC : Seems to be unused ... + if ($this->_compress) + $v_end_of_file = @gzeof($this->_file); + else + $v_end_of_file = @feof($this->_file); + */ + + if ($v_listing || $v_extract_file || $v_extraction_stopped) { + // ----- Log extracted files + if (($v_file_dir = dirname($v_header['filename'])) + == $v_header['filename']) + $v_file_dir = ''; + if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == '')) + $v_file_dir = '/'; + + $p_list_detail[$v_nb++] = $v_header; + if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) { + return true; + } + } + } + + return true; + } + // }}} + + // {{{ _openAppend() + function _openAppend() + { + if (filesize($this->_tarname) == 0) + return $this->_openWrite(); + + if ($this->_compress) { + $this->_close(); + + if (!@rename($this->_tarname, $this->_tarname.".tmp")) { + $this->_error('Error while renaming \''.$this->_tarname + .'\' to temporary file \''.$this->_tarname + .'.tmp\''); + return false; + } + + if ($this->_compress_type == 'gz') + $v_temp_tar = @gzopen($this->_tarname.".tmp", "rb"); + elseif ($this->_compress_type == 'bz2') + $v_temp_tar = @bzopen($this->_tarname.".tmp", "r"); + + if ($v_temp_tar == 0) { + $this->_error('Unable to open file \''.$this->_tarname + .'.tmp\' in binary read mode'); + @rename($this->_tarname.".tmp", $this->_tarname); + return false; + } + + if (!$this->_openWrite()) { + @rename($this->_tarname.".tmp", $this->_tarname); + return false; + } + + if ($this->_compress_type == 'gz') { + while (!@gzeof($v_temp_tar)) { + $v_buffer = @gzread($v_temp_tar, 512); + if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) { + // do not copy end blocks, we will re-make them + // after appending + continue; + } + $v_binary_data = pack("a512", $v_buffer); + $this->_writeBlock($v_binary_data); + } + + @gzclose($v_temp_tar); + } + elseif ($this->_compress_type == 'bz2') { + while (strlen($v_buffer = @bzread($v_temp_tar, 512)) > 0) { + if ($v_buffer == ARCHIVE_TAR_END_BLOCK) { + continue; + } + $v_binary_data = pack("a512", $v_buffer); + $this->_writeBlock($v_binary_data); + } + + @bzclose($v_temp_tar); + } + + if (!@unlink($this->_tarname.".tmp")) { + $this->_error('Error while deleting temporary file \'' + .$this->_tarname.'.tmp\''); + } + + } else { + // ----- For not compressed tar, just add files before the last + // one or two 512 bytes block + if (!$this->_openReadWrite()) + return false; + + clearstatcache(); + $v_size = filesize($this->_tarname); + + // We might have zero, one or two end blocks. + // The standard is two, but we should try to handle + // other cases. + fseek($this->_file, $v_size - 1024); + if (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) { + fseek($this->_file, $v_size - 1024); + } + elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) { + fseek($this->_file, $v_size - 512); + } + } + + return true; + } + // }}} + + // {{{ _append() + function _append($p_filelist, $p_add_dir='', $p_remove_dir='') + { + if (!$this->_openAppend()) + return false; + + if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir)) + $this->_writeFooter(); + + $this->_close(); + + return true; + } + // }}} + + // {{{ _dirCheck() + + /** + * Check if a directory exists and create it (including parent + * dirs) if not. + * + * @param string $p_dir directory to check + * + * @return bool true if the directory exists or was created + */ + function _dirCheck($p_dir) + { + clearstatcache(); + if ((@is_dir($p_dir)) || ($p_dir == '')) + return true; + + $p_parent_dir = dirname($p_dir); + + if (($p_parent_dir != $p_dir) && + ($p_parent_dir != '') && + (!$this->_dirCheck($p_parent_dir))) + return false; + + if (!@mkdir($p_dir, 0777)) { + $this->_error("Unable to create directory '$p_dir'"); + return false; + } + + return true; + } + + // }}} + + // {{{ _pathReduction() + + /** + * Compress path by changing for example "/dir/foo/../bar" to "/dir/bar", + * rand emove double slashes. + * + * @param string $p_dir path to reduce + * + * @return string reduced path + * + * @access private + * + */ + function _pathReduction($p_dir) + { + $v_result = ''; + + // ----- Look for not empty path + if ($p_dir != '') { + // ----- Explode path by directory names + $v_list = explode('/', $p_dir); + + // ----- Study directories from last to first + for ($i=sizeof($v_list)-1; $i>=0; $i--) { + // ----- Look for current path + if ($v_list[$i] == ".") { + // ----- Ignore this directory + // Should be the first $i=0, but no check is done + } + else if ($v_list[$i] == "..") { + // ----- Ignore it and ignore the $i-1 + $i--; + } + else if ( ($v_list[$i] == '') + && ($i!=(sizeof($v_list)-1)) + && ($i!=0)) { + // ----- Ignore only the double '//' in path, + // but not the first and last / + } else { + $v_result = $v_list[$i].($i!=(sizeof($v_list)-1)?'/' + .$v_result:''); + } + } + } + + if (defined('OS_WINDOWS') && OS_WINDOWS) { + $v_result = strtr($v_result, '\\', '/'); + } + + return $v_result; + } + + // }}} + + // {{{ _translateWinPath() + function _translateWinPath($p_path, $p_remove_disk_letter=true) + { + if (defined('OS_WINDOWS') && OS_WINDOWS) { + // ----- Look for potential disk letter + if ( ($p_remove_disk_letter) + && (($v_position = strpos($p_path, ':')) != false)) { + $p_path = substr($p_path, $v_position+1); + } + // ----- Change potential windows directory separator + if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0,1) == '\\')) { + $p_path = strtr($p_path, '\\', '/'); + } + } + return $p_path; + } + // }}} + +} +?> diff --git a/apps/media/getID3/getid3/extension.cache.dbm.php b/3rdparty/getid3/extension.cache.dbm.php similarity index 78% rename from apps/media/getID3/getid3/extension.cache.dbm.php rename to 3rdparty/getid3/extension.cache.dbm.php index c18b52d5dc..9a88c22b24 100644 --- a/apps/media/getID3/getid3/extension.cache.dbm.php +++ b/3rdparty/getid3/extension.cache.dbm.php @@ -77,35 +77,24 @@ class getID3_cached_dbm extends getID3 // Check for dba extension if (!extension_loaded('dba')) { - die('PHP is not compiled with dba support, required to use DBM style cache.'); + 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')) { // PHP 4.3.0+ - if (!in_array('db3', dba_handlers())) { - die('PHP is not compiled --with '.$cache_type.' support, required to use DBM style cache.'); - } - } - else { // PHP <= 4.2.3 - ob_start(); // nasty, buy the only way to check... - phpinfo(); - $contents = ob_get_contents(); - @ob_end_clean(); - if (!strstr($contents, $cache_type)) { - die('PHP is not compiled --with '.$cache_type.' support, required to use DBM style cache.'); - } + 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)) { - die('failed to create lock file: ' . $lock_filename); + throw new Exception('failed to create lock file: '.$lock_filename); } } // Open lock file for writing if (!is_writeable($lock_filename)) { - die('lock file: ' . $lock_filename . ' is not writable'); + throw new Exception('lock file: '.$lock_filename.' is not writable'); } $this->lock = fopen($lock_filename, 'w'); @@ -115,23 +104,23 @@ class getID3_cached_dbm extends getID3 // Create dbm-file if needed if (!file_exists($dbm_filename)) { if (!touch($dbm_filename)) { - die('failed to create dbm file: ' . $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); + $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) { - die('failed to create dbm file: ' . $dbm_filename); + throw new Exception('failed to create dbm file: '.$dbm_filename); } // Insert getID3 version number - dba_insert(GETID3_VERSION, GETID3_VERSION, $this->dba); + dba_insert(getID3::VERSION, getID3::VERSION, $this->dba); } // Init misc values @@ -142,7 +131,7 @@ class getID3_cached_dbm extends getID3 register_shutdown_function(array($this, '__destruct')); // Check version number and clear cache if changed - if (dba_fetch(GETID3_VERSION, $this->dba) != GETID3_VERSION) { + if (dba_fetch(getID3::VERSION, $this->dba) != getID3::VERSION) { $this->clear_cache(); } @@ -155,13 +144,13 @@ class getID3_cached_dbm extends getID3 function __destruct() { // Close dbm file - @dba_close($this->dba); + dba_close($this->dba); // Release exclusive lock - @flock($this->lock, LOCK_UN); + flock($this->lock, LOCK_UN); // Close lock file - @fclose($this->lock); + fclose($this->lock); } @@ -176,13 +165,13 @@ class getID3_cached_dbm extends getID3 $this->dba = dba_open($this->dbm_filename, 'n', $this->cache_type); if (!$this->dba) { - die('failed to clear cache/recreate dbm file: ' . $this->dbm_filename); + 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); + dba_insert(getID3::VERSION, getID3::VERSION, $this->dba); - // Reregister shutdown function + // Re-register shutdown function register_shutdown_function(array($this, '__destruct')); } @@ -194,7 +183,7 @@ class getID3_cached_dbm extends getID3 if (file_exists($filename)) { // Calc key filename::mod_time::size - should be unique - $key = $filename . '::' . filemtime($filename) . '::' . filesize($filename); + $key = $filename.'::'.filemtime($filename).'::'.filesize($filename); // Loopup key $result = dba_fetch($key, $this->dba); diff --git a/apps/media/getID3/getid3/extension.cache.mysql.php b/3rdparty/getid3/extension.cache.mysql.php similarity index 64% rename from apps/media/getID3/getid3/extension.cache.mysql.php rename to 3rdparty/getid3/extension.cache.mysql.php index 40ea6883ea..1e1f91fa14 100644 --- a/apps/media/getID3/getid3/extension.cache.mysql.php +++ b/3rdparty/getid3/extension.cache.mysql.php @@ -11,6 +11,7 @@ ///////////////////////////////////////////////////////////////// // // // This extension written by Allan Hansen // +// Table name mod by Carlo Capocasa // // /// ///////////////////////////////////////////////////////////////// @@ -33,8 +34,8 @@ * * require_once 'getid3/getid3.php'; * require_once 'getid3/getid3/extension.cache.mysql.php'; -* $getID3 = new getID3_cached_mysql('localhost', 'database', -* 'username', 'password'); +* // 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'); @@ -78,31 +79,36 @@ class getID3_cached_mysql extends getID3 // public: constructor - see top of this file for cache type and cache_options - function getID3_cached_mysql($host, $database, $username, $password) { + function getID3_cached_mysql($host, $database, $username, $password, $table='getid3_cache') { // Check for mysql support if (!function_exists('mysql_pconnect')) { - die('PHP not compiled with mysql support.'); + throw new Exception('PHP not compiled with mysql support.'); } // Connect to database $this->connection = mysql_pconnect($host, $username, $password); if (!$this->connection) { - die('mysql_pconnect() failed - check permissions and spelling.'); + throw new Exception('mysql_pconnect() failed - check permissions and spelling.'); } // Select database if (!mysql_select_db($database, $this->connection)) { - die('Cannot use database '.$database); + 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 - $this->cursor = mysql_query("SELECT `value` FROM `getid3_cache` WHERE (`filename` = '".GETID3_VERSION."') AND (`filesize` = '-1') AND (`filetime` = '-1') AND (`analyzetime` = '-1')", $this->connection); - list($version) = @mysql_fetch_array($this->cursor); - if ($version != GETID3_VERSION) { + $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(); } @@ -114,8 +120,8 @@ class getID3_cached_mysql extends getID3 // public: clear cache function clear_cache() { - $this->cursor = mysql_query("DELETE FROM `getid3_cache`", $this->connection); - $this->cursor = mysql_query("INSERT INTO `getid3_cache` VALUES ('".GETID3_VERSION."', -1, -1, -1, '".GETID3_VERSION."')", $this->connection); + $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); } @@ -128,35 +134,32 @@ class getID3_cached_mysql extends getID3 // Short-hands $filetime = filemtime($filename); $filesize = filesize($filename); - $filenam2 = mysql_escape_string($filename); - // Loopup file - $this->cursor = mysql_query("SELECT `value` FROM `getid3_cache` WHERE (`filename`='".$filenam2."') AND (`filesize`='".$filesize."') AND (`filetime`='".$filetime."')", $this->connection); - list($result) = @mysql_fetch_array($this->cursor); - - // Hit - if ($result) { - return unserialize($result); + // 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 - $result = parent::analyze($filename); + $analysis = parent::analyze($filename); // Save result if (file_exists($filename)) { - $res2 = mysql_escape_string(serialize($result)); - $this->cursor = mysql_query("INSERT INTO `getid3_cache` (`filename`, `filesize`, `filetime`, `analyzetime`, `value`) VALUES ('".$filenam2."', '".$filesize."', '".$filetime."', '".time()."', '".$res2."')", $this->connection); + $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 $result; + return $analysis; } // private: (re)create sql table - function create_table($drop = false) { + function create_table($drop=false) { - $this->cursor = mysql_query("CREATE TABLE IF NOT EXISTS `getid3_cache` ( + $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', @@ -167,5 +170,4 @@ class getID3_cached_mysql extends getID3 } } - ?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/getid3.lib.php b/3rdparty/getid3/getid3.lib.php similarity index 70% rename from apps/media/getID3/getid3/getid3.lib.php rename to 3rdparty/getid3/getid3.lib.php index 9322cae4dd..723e2e24c2 100644 --- a/apps/media/getID3/getid3/getid3.lib.php +++ b/3rdparty/getid3/getid3.lib.php @@ -14,33 +14,28 @@ class getid3_lib { - function PrintHexBytes($string, $hex=true, $spaces=true, $htmlsafe=true) { + static function PrintHexBytes($string, $hex=true, $spaces=true, $htmlencoding='UTF-8') { $returnstring = ''; for ($i = 0; $i < strlen($string); $i++) { if ($hex) { $returnstring .= str_pad(dechex(ord($string{$i})), 2, '0', STR_PAD_LEFT); } else { - $returnstring .= ' '.(ereg("[\x20-\x7E]", $string{$i}) ? $string{$i} : '¤'); + $returnstring .= ' '.(preg_match("#[\x20-\x7E]#", $string{$i}) ? $string{$i} : '¤'); } if ($spaces) { $returnstring .= ' '; } } - if ($htmlsafe) { - $returnstring = htmlentities($returnstring); + if (!empty($htmlencoding)) { + if ($htmlencoding === true) { + $htmlencoding = 'UTF-8'; // prior to getID3 v1.9.0 the function's 4th parameter was boolean + } + $returnstring = htmlentities($returnstring, ENT_QUOTES, $htmlencoding); } return $returnstring; } - function SafeStripSlashes($text) { - if (get_magic_quotes_gpc()) { - return stripslashes($text); - } - return $text; - } - - - function trunc($floatnumber) { + static function trunc($floatnumber) { // truncates a floating-point number at the decimal point // returns int (if possible, otherwise float) if ($floatnumber >= 1) { @@ -50,21 +45,30 @@ class getid3_lib } else { $truncatednumber = 0; } - if ($truncatednumber <= 1073741824) { // 2^30 + if (getid3_lib::intValueSupported($truncatednumber)) { $truncatednumber = (int) $truncatednumber; } return $truncatednumber; } - function CastAsInt($floatnum) { + static function safe_inc(&$variable, $increment=1) { + if (isset($variable)) { + $variable += $increment; + } else { + $variable = $increment; + } + return true; + } + + static function CastAsInt($floatnum) { // convert to float if not already $floatnum = (float) $floatnum; // convert a float to type int, only if possible if (getid3_lib::trunc($floatnum) == $floatnum) { // it's not floating point - if ($floatnum <= 2147483647) { // 2^31 + if (getid3_lib::intValueSupported($floatnum)) { // it's within int range $floatnum = (int) $floatnum; } @@ -72,15 +76,36 @@ class getid3_lib return $floatnum; } + public static function intValueSupported($num) { + // check if integers are 64-bit + static $hasINT64 = null; + if ($hasINT64 === null) { // 10x faster than is_null() + $hasINT64 = is_int(pow(2, 31)); // 32-bit int are limited to (2^31)-1 + if (!$hasINT64 && !defined('PHP_INT_MIN')) { + define('PHP_INT_MIN', ~PHP_INT_MAX); + } + } + // if integers are 64-bit - no other check required + if ($hasINT64 || (($num <= PHP_INT_MAX) && ($num >= PHP_INT_MIN))) { + return true; + } + return false; + } - function DecimalBinary2Float($binarynumerator) { + static function DecimalizeFraction($fraction) { + list($numerator, $denominator) = explode('/', $fraction); + return $numerator / ($denominator ? $denominator : 1); + } + + + static function DecimalBinary2Float($binarynumerator) { $numerator = getid3_lib::Bin2Dec($binarynumerator); $denominator = getid3_lib::Bin2Dec('1'.str_repeat('0', strlen($binarynumerator))); return ($numerator / $denominator); } - function NormalizeBinaryPoint($binarypointnumber, $maxbits=52) { + static function NormalizeBinaryPoint($binarypointnumber, $maxbits=52) { // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html if (strpos($binarypointnumber, '.') === false) { $binarypointnumber = '0.'.$binarypointnumber; @@ -104,7 +129,7 @@ class getid3_lib } - function Float2BinaryDecimal($floatvalue) { + static function Float2BinaryDecimal($floatvalue) { // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html $maxbits = 128; // to how many bits of precision should the calculations be taken? $intpart = getid3_lib::trunc($floatvalue); @@ -120,7 +145,7 @@ class getid3_lib } - function Float2String($floatvalue, $bits) { + static function Float2String($floatvalue, $bits) { // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee-expl.html switch ($bits) { case 32: @@ -151,20 +176,20 @@ class getid3_lib } - function LittleEndian2Float($byteword) { + static function LittleEndian2Float($byteword) { return getid3_lib::BigEndian2Float(strrev($byteword)); } - function BigEndian2Float($byteword) { + static function BigEndian2Float($byteword) { // ANSI/IEEE Standard 754-1985, Standard for Binary Floating Point Arithmetic // http://www.psc.edu/general/software/packages/ieee/ieee.html // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee.html $bitword = getid3_lib::BigEndian2Bin($byteword); if (!$bitword) { - return 0; - } + return 0; + } $signbit = $bitword{0}; switch (strlen($byteword) * 8) { @@ -234,44 +259,42 @@ class getid3_lib } - function BigEndian2Int($byteword, $synchsafe=false, $signed=false) { + static function BigEndian2Int($byteword, $synchsafe=false, $signed=false) { $intvalue = 0; $bytewordlen = strlen($byteword); + if ($bytewordlen == 0) { + return false; + } for ($i = 0; $i < $bytewordlen; $i++) { if ($synchsafe) { // disregard MSB, effectively 7-bit bytes - $intvalue = $intvalue | (ord($byteword{$i}) & 0x7F) << (($bytewordlen - 1 - $i) * 7); + //$intvalue = $intvalue | (ord($byteword{$i}) & 0x7F) << (($bytewordlen - 1 - $i) * 7); // faster, but runs into problems past 2^31 on 32-bit systems + $intvalue += (ord($byteword{$i}) & 0x7F) * pow(2, ($bytewordlen - 1 - $i) * 7); } else { $intvalue += ord($byteword{$i}) * pow(256, ($bytewordlen - 1 - $i)); } } if ($signed && !$synchsafe) { // synchsafe ints are not allowed to be signed - switch ($bytewordlen) { - case 1: - case 2: - case 3: - case 4: - $signmaskbit = 0x80 << (8 * ($bytewordlen - 1)); - if ($intvalue & $signmaskbit) { - $intvalue = 0 - ($intvalue & ($signmaskbit - 1)); - } - break; - - default: - die('ERROR: Cannot have signed integers larger than 32-bits in getid3_lib::BigEndian2Int()'); - break; + if ($bytewordlen <= PHP_INT_SIZE) { + $signMaskBit = 0x80 << (8 * ($bytewordlen - 1)); + if ($intvalue & $signMaskBit) { + $intvalue = 0 - ($intvalue & ($signMaskBit - 1)); + } + } else { + throw new Exception('ERROR: Cannot have signed integers larger than '.(8 * PHP_INT_SIZE).'-bits ('.strlen($byteword).') in getid3_lib::BigEndian2Int()'); + break; } } return getid3_lib::CastAsInt($intvalue); } - function LittleEndian2Int($byteword, $signed=false) { + static function LittleEndian2Int($byteword, $signed=false) { return getid3_lib::BigEndian2Int(strrev($byteword), false, $signed); } - function BigEndian2Bin($byteword) { + static function BigEndian2Bin($byteword) { $binvalue = ''; $bytewordlen = strlen($byteword); for ($i = 0; $i < $bytewordlen; $i++) { @@ -281,15 +304,15 @@ class getid3_lib } - function BigEndian2String($number, $minbytes=1, $synchsafe=false, $signed=false) { + static function BigEndian2String($number, $minbytes=1, $synchsafe=false, $signed=false) { if ($number < 0) { - return false; + throw new Exception('ERROR: getid3_lib::BigEndian2String() does not support negative numbers'); } $maskbyte = (($synchsafe || $signed) ? 0x7F : 0xFF); $intstring = ''; if ($signed) { - if ($minbytes > 4) { - die('ERROR: Cannot have signed integers larger than 32-bits in getid3_lib::BigEndian2String()'); + if ($minbytes > PHP_INT_SIZE) { + throw new Exception('ERROR: Cannot have signed integers larger than '.(8 * PHP_INT_SIZE).'-bits in getid3_lib::BigEndian2String()'); } $number = $number & (0x80 << (8 * ($minbytes - 1))); } @@ -302,7 +325,7 @@ class getid3_lib } - function Dec2Bin($number) { + static function Dec2Bin($number) { while ($number >= 256) { $bytes[] = (($number / 256) - (floor($number / 256))) * 256; $number = floor($number / 256); @@ -316,7 +339,7 @@ class getid3_lib } - function Bin2Dec($binstring, $signed=false) { + static function Bin2Dec($binstring, $signed=false) { $signmult = 1; if ($signed) { if ($binstring{0} == '1') { @@ -332,7 +355,7 @@ class getid3_lib } - function Bin2String($binstring) { + static function Bin2String($binstring) { // return 'hi' for input of '0110100001101001' $string = ''; $binstringreversed = strrev($binstring); @@ -343,7 +366,7 @@ class getid3_lib } - function LittleEndian2String($number, $minbytes=1, $synchsafe=false) { + static function LittleEndian2String($number, $minbytes=1, $synchsafe=false) { $intstring = ''; while ($number > 0) { if ($synchsafe) { @@ -358,7 +381,7 @@ class getid3_lib } - function array_merge_clobber($array1, $array2) { + static function array_merge_clobber($array1, $array2) { // written by kcØhireability*com // taken from http://www.php.net/manual/en/function.array-merge-recursive.php if (!is_array($array1) || !is_array($array2)) { @@ -376,7 +399,7 @@ class getid3_lib } - function array_merge_noclobber($array1, $array2) { + static function array_merge_noclobber($array1, $array2) { if (!is_array($array1) || !is_array($array2)) { return false; } @@ -392,7 +415,17 @@ class getid3_lib } - function fileextension($filename, $numextensions=1) { + static function ksort_recursive(&$theArray) { + ksort($theArray); + foreach ($theArray as $key => $value) { + if (is_array($value)) { + self::ksort_recursive($theArray[$key]); + } + } + return true; + } + + static function fileextension($filename, $numextensions=1) { if (strstr($filename, '.')) { $reversedfilename = strrev($filename); $offset = 0; @@ -408,66 +441,40 @@ class getid3_lib } - function PlaytimeString($playtimeseconds) { - $sign = (($playtimeseconds < 0) ? '-' : ''); - $playtimeseconds = abs($playtimeseconds); - $contentseconds = round((($playtimeseconds / 60) - floor($playtimeseconds / 60)) * 60); - $contentminutes = floor($playtimeseconds / 60); - if ($contentseconds >= 60) { - $contentseconds -= 60; - $contentminutes++; - } - return $sign.intval($contentminutes).':'.str_pad($contentseconds, 2, 0, STR_PAD_LEFT); + static function PlaytimeString($seconds) { + $sign = (($seconds < 0) ? '-' : ''); + $seconds = abs($seconds); + $H = floor( $seconds / 3600); + $M = floor(($seconds - (3600 * $H) ) / 60); + $S = round( $seconds - (3600 * $H) - (60 * $M) ); + return $sign.($H ? $H.':' : '').($H ? str_pad($M, 2, '0', STR_PAD_LEFT) : intval($M)).':'.str_pad($S, 2, 0, STR_PAD_LEFT); } - function image_type_to_mime_type($imagetypeid) { - // only available in PHP v4.3.0+ - static $image_type_to_mime_type = array(); - if (empty($image_type_to_mime_type)) { - $image_type_to_mime_type[1] = 'image/gif'; // GIF - $image_type_to_mime_type[2] = 'image/jpeg'; // JPEG - $image_type_to_mime_type[3] = 'image/png'; // PNG - $image_type_to_mime_type[4] = 'application/x-shockwave-flash'; // Flash - $image_type_to_mime_type[5] = 'image/psd'; // PSD - $image_type_to_mime_type[6] = 'image/bmp'; // BMP - $image_type_to_mime_type[7] = 'image/tiff'; // TIFF: little-endian (Intel) - $image_type_to_mime_type[8] = 'image/tiff'; // TIFF: big-endian (Motorola) - //$image_type_to_mime_type[9] = 'image/jpc'; // JPC - //$image_type_to_mime_type[10] = 'image/jp2'; // JPC - //$image_type_to_mime_type[11] = 'image/jpx'; // JPC - //$image_type_to_mime_type[12] = 'image/jb2'; // JPC - $image_type_to_mime_type[13] = 'application/x-shockwave-flash'; // Shockwave - $image_type_to_mime_type[14] = 'image/iff'; // IFF - } - return (isset($image_type_to_mime_type[$imagetypeid]) ? $image_type_to_mime_type[$imagetypeid] : 'application/octet-stream'); - } - - - function DateMac2Unix($macdate) { + static function DateMac2Unix($macdate) { // Macintosh timestamp: seconds since 00:00h January 1, 1904 // UNIX timestamp: seconds since 00:00h January 1, 1970 return getid3_lib::CastAsInt($macdate - 2082844800); } - function FixedPoint8_8($rawdata) { + static function FixedPoint8_8($rawdata) { return getid3_lib::BigEndian2Int(substr($rawdata, 0, 1)) + (float) (getid3_lib::BigEndian2Int(substr($rawdata, 1, 1)) / pow(2, 8)); } - function FixedPoint16_16($rawdata) { + static function FixedPoint16_16($rawdata) { return getid3_lib::BigEndian2Int(substr($rawdata, 0, 2)) + (float) (getid3_lib::BigEndian2Int(substr($rawdata, 2, 2)) / pow(2, 16)); } - function FixedPoint2_30($rawdata) { + static function FixedPoint2_30($rawdata) { $binarystring = getid3_lib::BigEndian2Bin($rawdata); - return getid3_lib::Bin2Dec(substr($binarystring, 0, 2)) + (float) (getid3_lib::Bin2Dec(substr($binarystring, 2, 30)) / 1073741824); + return getid3_lib::Bin2Dec(substr($binarystring, 0, 2)) + (float) (getid3_lib::Bin2Dec(substr($binarystring, 2, 30)) / pow(2, 30)); } - function CreateDeepArray($ArrayPath, $Separator, $Value) { + static function CreateDeepArray($ArrayPath, $Separator, $Value) { // assigns $Value to a nested array path: // $foo = getid3_lib::CreateDeepArray('/path/to/my', '/', 'file.txt') // is the same as: @@ -485,7 +492,7 @@ class getid3_lib return $ReturnedArray; } - function array_max($arraydata, $returnkey=false) { + static function array_max($arraydata, $returnkey=false) { $maxvalue = false; $maxkey = false; foreach ($arraydata as $key => $value) { @@ -499,7 +506,7 @@ class getid3_lib return ($returnkey ? $maxkey : $maxvalue); } - function array_min($arraydata, $returnkey=false) { + static function array_min($arraydata, $returnkey=false) { $minvalue = false; $minkey = false; foreach ($arraydata as $key => $value) { @@ -513,82 +520,35 @@ class getid3_lib return ($returnkey ? $minkey : $minvalue); } - - function md5_file($file) { - - // md5_file() exists in PHP 4.2.0+. - if (function_exists('md5_file')) { - return md5_file($file); - } - - if (GETID3_OS_ISWINDOWS) { - - $RequiredFiles = array('cygwin1.dll', 'md5sum.exe'); - foreach ($RequiredFiles as $required_file) { - if (!is_readable(GETID3_HELPERAPPSDIR.$required_file)) { - die(implode(' and ', $RequiredFiles).' are required in '.GETID3_HELPERAPPSDIR.' for getid3_lib::md5_file() to function under Windows in PHP < v4.2.0'); - } + static function XML2array($XMLstring) { + if (function_exists('simplexml_load_string')) { + if (function_exists('get_object_vars')) { + $XMLobject = simplexml_load_string($XMLstring); + return self::SimpleXMLelement2array($XMLobject); } - $commandline = GETID3_HELPERAPPSDIR.'md5sum.exe "'.str_replace('/', DIRECTORY_SEPARATOR, $file).'"'; - if (ereg("^[\\]?([0-9a-f]{32})", strtolower(`$commandline`), $r)) { - return $r[1]; - } - - } else { - - // The following works under UNIX only - $file = str_replace('`', '\\`', $file); - if (ereg("^([0-9a-f]{32})[ \t\n\r]", `md5sum "$file"`, $r)) { - return $r[1]; - } - } return false; } - - function sha1_file($file) { - - // sha1_file() exists in PHP 4.3.0+. - if (function_exists('sha1_file')) { - return sha1_file($file); + static function SimpleXMLelement2array($XMLobject) { + if (!is_object($XMLobject) && !is_array($XMLobject)) { + return $XMLobject; } - - $file = str_replace('`', '\\`', $file); - - if (GETID3_OS_ISWINDOWS) { - - $RequiredFiles = array('cygwin1.dll', 'sha1sum.exe'); - foreach ($RequiredFiles as $required_file) { - if (!is_readable(GETID3_HELPERAPPSDIR.$required_file)) { - die(implode(' and ', $RequiredFiles).' are required in '.GETID3_HELPERAPPSDIR.' for getid3_lib::sha1_file() to function under Windows in PHP < v4.3.0'); - } - } - $commandline = GETID3_HELPERAPPSDIR.'sha1sum.exe "'.str_replace('/', DIRECTORY_SEPARATOR, $file).'"'; - if (ereg("^sha1=([0-9a-f]{40})", strtolower(`$commandline`), $r)) { - return $r[1]; - } - - } else { - - $commandline = 'sha1sum '.escapeshellarg($file).''; - if (ereg("^([0-9a-f]{40})[ \t\n\r]", strtolower(`$commandline`), $r)) { - return $r[1]; - } - + $XMLarray = (is_object($XMLobject) ? get_object_vars($XMLobject) : $XMLobject); + foreach ($XMLarray as $key => $value) { + $XMLarray[$key] = self::SimpleXMLelement2array($value); } - - return false; + return $XMLarray; } // Allan Hansen // getid3_lib::md5_data() - returns md5sum for a file from startuing position to absolute end position - function hash_data($file, $offset, $end, $algorithm) { - if ($end >= pow(2, 31)) { + static function hash_data($file, $offset, $end, $algorithm) { + static $tempdir = ''; + if (!getid3_lib::intValueSupported($end)) { return false; } - switch ($algorithm) { case 'md5': $hash_function = 'md5_file'; @@ -605,7 +565,7 @@ class getid3_lib break; default: - die('Invalid algorithm ('.$algorithm.') in getid3_lib::hash_data()'); + throw new Exception('Invalid algorithm ('.$algorithm.') in getid3_lib::hash_data()'); break; } $size = $end - $offset; @@ -636,16 +596,23 @@ class getid3_lib $commandline .= $unix_call; } - if ((bool) ini_get('safe_mode')) { - $ThisFileInfo['warning'][] = 'PHP running in Safe Mode - backtick operator not available, using slower non-system-call '.$algorithm.' algorithm'; + if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { + //throw new Exception('PHP running in Safe Mode - backtick operator not available, using slower non-system-call '.$algorithm.' algorithm'); break; } return substr(`$commandline`, 0, $hash_length); } + if (empty($tempdir)) { + // yes this is ugly, feel free to suggest a better way + require_once(dirname(__FILE__).'/getid3.php'); + $getid3_temp = new getID3(); + $tempdir = $getid3_temp->tempdir; + unset($getid3_temp); + } // try to create a temporary file in the system temp directory - invalid dirname should force to system temp dir - if (($data_filename = tempnam('*', 'getID3')) === false) { - // can't find anywhere to create a temp file, just die + if (($data_filename = tempnam($tempdir, 'gI3')) === false) { + // can't find anywhere to create a temp file, just fail return false; } @@ -653,52 +620,68 @@ class getid3_lib $result = false; // copy parts of file - if ($fp = @fopen($file, 'rb')) { - - if ($fp_data = @fopen($data_filename, 'wb')) { - - fseek($fp, $offset, SEEK_SET); - $byteslefttowrite = $end - $offset; - while (($byteslefttowrite > 0) && ($buffer = fread($fp, GETID3_FREAD_BUFFER_SIZE))) { - $byteswritten = fwrite($fp_data, $buffer, $byteslefttowrite); - $byteslefttowrite -= $byteswritten; - } - fclose($fp_data); - $result = getid3_lib::$hash_function($data_filename); - - } - fclose($fp); + try { + getid3_lib::CopyFileParts($file, $data_filename, $offset, $end - $offset); + $result = $hash_function($data_filename); + } catch (Exception $e) { + throw new Exception('getid3_lib::CopyFileParts() failed in getid_lib::hash_data(): '.$e->getMessage()); } unlink($data_filename); return $result; } + static function CopyFileParts($filename_source, $filename_dest, $offset, $length) { + if (!getid3_lib::intValueSupported($offset + $length)) { + throw new Exception('cannot copy file portion, it extends beyond the '.round(PHP_INT_MAX / 1073741824).'GB limit'); + } + if (is_readable($filename_source) && is_file($filename_source) && ($fp_src = fopen($filename_source, 'rb'))) { + if (($fp_dest = fopen($filename_dest, 'wb'))) { + if (fseek($fp_src, $offset, SEEK_SET) == 0) { + $byteslefttowrite = $length; + while (($byteslefttowrite > 0) && ($buffer = fread($fp_src, min($byteslefttowrite, getID3::FREAD_BUFFER_SIZE)))) { + $byteswritten = fwrite($fp_dest, $buffer, $byteslefttowrite); + $byteslefttowrite -= $byteswritten; + } + return true; + } else { + throw new Exception('failed to seek to offset '.$offset.' in '.$filename_source); + } + fclose($fp_dest); + } else { + throw new Exception('failed to create file for writing '.$filename_dest); + } + fclose($fp_src); + } else { + throw new Exception('failed to open file for reading '.$filename_source); + } + return false; + } - function iconv_fallback_int_utf8($charval) { + static function iconv_fallback_int_utf8($charval) { if ($charval < 128) { // 0bbbbbbb $newcharstring = chr($charval); } elseif ($charval < 2048) { // 110bbbbb 10bbbbbb - $newcharstring = chr(($charval >> 6) | 0xC0); + $newcharstring = chr(($charval >> 6) | 0xC0); $newcharstring .= chr(($charval & 0x3F) | 0x80); } elseif ($charval < 65536) { // 1110bbbb 10bbbbbb 10bbbbbb - $newcharstring = chr(($charval >> 12) | 0xE0); - $newcharstring .= chr(($charval >> 6) | 0xC0); + $newcharstring = chr(($charval >> 12) | 0xE0); + $newcharstring .= chr(($charval >> 6) | 0xC0); $newcharstring .= chr(($charval & 0x3F) | 0x80); } else { // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb - $newcharstring = chr(($charval >> 18) | 0xF0); - $newcharstring .= chr(($charval >> 12) | 0xC0); - $newcharstring .= chr(($charval >> 6) | 0xC0); + $newcharstring = chr(($charval >> 18) | 0xF0); + $newcharstring .= chr(($charval >> 12) | 0xC0); + $newcharstring .= chr(($charval >> 6) | 0xC0); $newcharstring .= chr(($charval & 0x3F) | 0x80); } return $newcharstring; } // ISO-8859-1 => UTF-8 - function iconv_fallback_iso88591_utf8($string, $bom=false) { + static function iconv_fallback_iso88591_utf8($string, $bom=false) { if (function_exists('utf8_encode')) { return utf8_encode($string); } @@ -715,7 +698,7 @@ class getid3_lib } // ISO-8859-1 => UTF-16BE - function iconv_fallback_iso88591_utf16be($string, $bom=false) { + static function iconv_fallback_iso88591_utf16be($string, $bom=false) { $newcharstring = ''; if ($bom) { $newcharstring .= "\xFE\xFF"; @@ -727,7 +710,7 @@ class getid3_lib } // ISO-8859-1 => UTF-16LE - function iconv_fallback_iso88591_utf16le($string, $bom=false) { + static function iconv_fallback_iso88591_utf16le($string, $bom=false) { $newcharstring = ''; if ($bom) { $newcharstring .= "\xFF\xFE"; @@ -739,12 +722,12 @@ class getid3_lib } // ISO-8859-1 => UTF-16LE (BOM) - function iconv_fallback_iso88591_utf16($string) { + static function iconv_fallback_iso88591_utf16($string) { return getid3_lib::iconv_fallback_iso88591_utf16le($string, true); } // UTF-8 => ISO-8859-1 - function iconv_fallback_utf8_iso88591($string) { + static function iconv_fallback_utf8_iso88591($string) { if (function_exists('utf8_decode')) { return utf8_decode($string); } @@ -756,20 +739,20 @@ class getid3_lib if ((ord($string{$offset}) | 0x07) == 0xF7) { // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb $charval = ((ord($string{($offset + 0)}) & 0x07) << 18) & - ((ord($string{($offset + 1)}) & 0x3F) << 12) & - ((ord($string{($offset + 2)}) & 0x3F) << 6) & - (ord($string{($offset + 3)}) & 0x3F); + ((ord($string{($offset + 1)}) & 0x3F) << 12) & + ((ord($string{($offset + 2)}) & 0x3F) << 6) & + (ord($string{($offset + 3)}) & 0x3F); $offset += 4; } elseif ((ord($string{$offset}) | 0x0F) == 0xEF) { // 1110bbbb 10bbbbbb 10bbbbbb $charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) & - ((ord($string{($offset + 1)}) & 0x3F) << 6) & - (ord($string{($offset + 2)}) & 0x3F); + ((ord($string{($offset + 1)}) & 0x3F) << 6) & + (ord($string{($offset + 2)}) & 0x3F); $offset += 3; } elseif ((ord($string{$offset}) | 0x1F) == 0xDF) { // 110bbbbb 10bbbbbb $charval = ((ord($string{($offset + 0)}) & 0x1F) << 6) & - (ord($string{($offset + 1)}) & 0x3F); + (ord($string{($offset + 1)}) & 0x3F); $offset += 2; } elseif ((ord($string{$offset}) | 0x7F) == 0x7F) { // 0bbbbbbb @@ -788,7 +771,7 @@ class getid3_lib } // UTF-8 => UTF-16BE - function iconv_fallback_utf8_utf16be($string, $bom=false) { + static function iconv_fallback_utf8_utf16be($string, $bom=false) { $newcharstring = ''; if ($bom) { $newcharstring .= "\xFE\xFF"; @@ -799,20 +782,20 @@ class getid3_lib if ((ord($string{$offset}) | 0x07) == 0xF7) { // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb $charval = ((ord($string{($offset + 0)}) & 0x07) << 18) & - ((ord($string{($offset + 1)}) & 0x3F) << 12) & - ((ord($string{($offset + 2)}) & 0x3F) << 6) & - (ord($string{($offset + 3)}) & 0x3F); + ((ord($string{($offset + 1)}) & 0x3F) << 12) & + ((ord($string{($offset + 2)}) & 0x3F) << 6) & + (ord($string{($offset + 3)}) & 0x3F); $offset += 4; } elseif ((ord($string{$offset}) | 0x0F) == 0xEF) { // 1110bbbb 10bbbbbb 10bbbbbb $charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) & - ((ord($string{($offset + 1)}) & 0x3F) << 6) & - (ord($string{($offset + 2)}) & 0x3F); + ((ord($string{($offset + 1)}) & 0x3F) << 6) & + (ord($string{($offset + 2)}) & 0x3F); $offset += 3; } elseif ((ord($string{$offset}) | 0x1F) == 0xDF) { // 110bbbbb 10bbbbbb $charval = ((ord($string{($offset + 0)}) & 0x1F) << 6) & - (ord($string{($offset + 1)}) & 0x3F); + (ord($string{($offset + 1)}) & 0x3F); $offset += 2; } elseif ((ord($string{$offset}) | 0x7F) == 0x7F) { // 0bbbbbbb @@ -831,7 +814,7 @@ class getid3_lib } // UTF-8 => UTF-16LE - function iconv_fallback_utf8_utf16le($string, $bom=false) { + static function iconv_fallback_utf8_utf16le($string, $bom=false) { $newcharstring = ''; if ($bom) { $newcharstring .= "\xFF\xFE"; @@ -842,20 +825,20 @@ class getid3_lib if ((ord($string{$offset}) | 0x07) == 0xF7) { // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb $charval = ((ord($string{($offset + 0)}) & 0x07) << 18) & - ((ord($string{($offset + 1)}) & 0x3F) << 12) & - ((ord($string{($offset + 2)}) & 0x3F) << 6) & - (ord($string{($offset + 3)}) & 0x3F); + ((ord($string{($offset + 1)}) & 0x3F) << 12) & + ((ord($string{($offset + 2)}) & 0x3F) << 6) & + (ord($string{($offset + 3)}) & 0x3F); $offset += 4; } elseif ((ord($string{$offset}) | 0x0F) == 0xEF) { // 1110bbbb 10bbbbbb 10bbbbbb $charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) & - ((ord($string{($offset + 1)}) & 0x3F) << 6) & - (ord($string{($offset + 2)}) & 0x3F); + ((ord($string{($offset + 1)}) & 0x3F) << 6) & + (ord($string{($offset + 2)}) & 0x3F); $offset += 3; } elseif ((ord($string{$offset}) | 0x1F) == 0xDF) { // 110bbbbb 10bbbbbb $charval = ((ord($string{($offset + 0)}) & 0x1F) << 6) & - (ord($string{($offset + 1)}) & 0x3F); + (ord($string{($offset + 1)}) & 0x3F); $offset += 2; } elseif ((ord($string{$offset}) | 0x7F) == 0x7F) { // 0bbbbbbb @@ -874,12 +857,12 @@ class getid3_lib } // UTF-8 => UTF-16LE (BOM) - function iconv_fallback_utf8_utf16($string) { + static function iconv_fallback_utf8_utf16($string) { return getid3_lib::iconv_fallback_utf8_utf16le($string, true); } // UTF-16BE => UTF-8 - function iconv_fallback_utf16be_utf8($string) { + static function iconv_fallback_utf16be_utf8($string) { if (substr($string, 0, 2) == "\xFE\xFF") { // strip BOM $string = substr($string, 2); @@ -893,7 +876,7 @@ class getid3_lib } // UTF-16LE => UTF-8 - function iconv_fallback_utf16le_utf8($string) { + static function iconv_fallback_utf16le_utf8($string) { if (substr($string, 0, 2) == "\xFF\xFE") { // strip BOM $string = substr($string, 2); @@ -907,7 +890,7 @@ class getid3_lib } // UTF-16BE => ISO-8859-1 - function iconv_fallback_utf16be_iso88591($string) { + static function iconv_fallback_utf16be_iso88591($string) { if (substr($string, 0, 2) == "\xFE\xFF") { // strip BOM $string = substr($string, 2); @@ -921,7 +904,7 @@ class getid3_lib } // UTF-16LE => ISO-8859-1 - function iconv_fallback_utf16le_iso88591($string) { + static function iconv_fallback_utf16le_iso88591($string) { if (substr($string, 0, 2) == "\xFF\xFE") { // strip BOM $string = substr($string, 2); @@ -935,7 +918,7 @@ class getid3_lib } // UTF-16 (BOM) => ISO-8859-1 - function iconv_fallback_utf16_iso88591($string) { + static function iconv_fallback_utf16_iso88591($string) { $bom = substr($string, 0, 2); if ($bom == "\xFE\xFF") { return getid3_lib::iconv_fallback_utf16be_iso88591(substr($string, 2)); @@ -946,7 +929,7 @@ class getid3_lib } // UTF-16 (BOM) => UTF-8 - function iconv_fallback_utf16_utf8($string) { + static function iconv_fallback_utf16_utf8($string) { $bom = substr($string, 0, 2); if ($bom == "\xFE\xFF") { return getid3_lib::iconv_fallback_utf16be_utf8(substr($string, 2)); @@ -956,7 +939,7 @@ class getid3_lib return $string; } - function iconv_fallback($in_charset, $out_charset, $string) { + static function iconv_fallback($in_charset, $out_charset, $string) { if ($in_charset == $out_charset) { return $string; @@ -964,23 +947,22 @@ class getid3_lib // iconv() availble if (function_exists('iconv')) { + if ($converted_string = @iconv($in_charset, $out_charset.'//TRANSLIT', $string)) { + switch ($out_charset) { + case 'ISO-8859-1': + $converted_string = rtrim($converted_string, "\x00"); + break; + } + return $converted_string; + } - if ($converted_string = @iconv($in_charset, $out_charset.'//TRANSLIT', $string)) { - switch ($out_charset) { - case 'ISO-8859-1': - $converted_string = rtrim($converted_string, "\x00"); - break; - } - return $converted_string; - } - - // iconv() may sometimes fail with "illegal character in input string" error message - // and return an empty string, but returning the unconverted string is more useful - return $string; - } + // iconv() may sometimes fail with "illegal character in input string" error message + // and return an empty string, but returning the unconverted string is more useful + return $string; + } - // iconv() not available + // iconv() not available static $ConversionFunctionList = array(); if (empty($ConversionFunctionList)) { $ConversionFunctionList['ISO-8859-1']['UTF-8'] = 'iconv_fallback_iso88591_utf8'; @@ -1002,41 +984,42 @@ class getid3_lib $ConversionFunction = $ConversionFunctionList[strtoupper($in_charset)][strtoupper($out_charset)]; return getid3_lib::$ConversionFunction($string); } - die('PHP does not have iconv() support - cannot convert from '.$in_charset.' to '.$out_charset); + throw new Exception('PHP does not have iconv() support - cannot convert from '.$in_charset.' to '.$out_charset); } static function MultiByteCharString2HTML($string, $charset='ISO-8859-1') { + $string = (string) $string; // in case trying to pass a numeric (float, int) string, would otherwise return an empty string $HTMLstring = ''; switch ($charset) { - case 'ISO-8859-1': - case 'ISO8859-1': - case 'ISO-8859-15': - case 'ISO8859-15': - case 'cp866': - case 'ibm866': - case '866': - case 'cp1251': - case 'Windows-1251': - case 'win-1251': case '1251': - case 'cp1252': - case 'Windows-1252': case '1252': + case '866': + case '932': + case '936': + case '950': + case 'BIG5': + case 'BIG5-HKSCS': + case 'cp1251': + case 'cp1252': + case 'cp866': + case 'EUC-JP': + case 'EUCJP': + case 'GB2312': + case 'ibm866': + case 'ISO-8859-1': + case 'ISO-8859-15': + case 'ISO8859-1': + case 'ISO8859-15': case 'KOI8-R': case 'koi8-ru': case 'koi8r': - case 'BIG5': - case '950': - case 'GB2312': - case '936': - case 'BIG5-HKSCS': case 'Shift_JIS': case 'SJIS': - case '932': - case 'EUC-JP': - case 'EUCJP': + case 'win-1251': + case 'Windows-1251': + case 'Windows-1252': $HTMLstring = htmlentities($string, ENT_COMPAT, $charset); break; @@ -1099,7 +1082,7 @@ class getid3_lib - function RGADnameLookup($namecode) { + static function RGADnameLookup($namecode) { static $RGADname = array(); if (empty($RGADname)) { $RGADname[0] = 'not set'; @@ -1111,7 +1094,7 @@ class getid3_lib } - function RGADoriginatorLookup($originatorcode) { + static function RGADoriginatorLookup($originatorcode) { static $RGADoriginator = array(); if (empty($RGADoriginator)) { $RGADoriginator[0] = 'unspecified'; @@ -1124,7 +1107,7 @@ class getid3_lib } - function RGADadjustmentLookup($rawadjustment, $signbit) { + static function RGADadjustmentLookup($rawadjustment, $signbit) { $adjustment = $rawadjustment / 10; if ($signbit == 1) { $adjustment *= -1; @@ -1133,7 +1116,7 @@ class getid3_lib } - function RGADgainString($namecode, $originatorcode, $replaygain) { + static function RGADgainString($namecode, $originatorcode, $replaygain) { if ($replaygain < 0) { $signbit = '1'; } else { @@ -1148,15 +1131,23 @@ class getid3_lib return $gainstring; } - function RGADamplitude2dB($amplitude) { + static function RGADamplitude2dB($amplitude) { return 20 * log10($amplitude); } - function GetDataImageSize($imgData, &$imageinfo) { + static function GetDataImageSize($imgData, &$imageinfo) { + static $tempdir = ''; + if (empty($tempdir)) { + // yes this is ugly, feel free to suggest a better way + require_once(dirname(__FILE__).'/getid3.php'); + $getid3_temp = new getID3(); + $tempdir = $getid3_temp->tempdir; + unset($getid3_temp); + } $GetDataImageSize = false; - if ($tempfilename = tempnam('*', 'getID3')) { - if ($tmp = @fopen($tempfilename, 'wb')) { + if ($tempfilename = tempnam($tempdir, 'gI3')) { + if (is_writable($tempfilename) && is_file($tempfilename) && ($tmp = fopen($tempfilename, 'wb'))) { fwrite($tmp, $imgData); fclose($tmp); $GetDataImageSize = @GetImageSize($tempfilename, $imageinfo); @@ -1166,7 +1157,7 @@ class getid3_lib return $GetDataImageSize; } - function ImageTypesLookup($imagetypeid) { + static function ImageTypesLookup($imagetypeid) { static $ImageTypesLookup = array(); if (empty($ImageTypesLookup)) { $ImageTypesLookup[1] = 'gif'; @@ -1210,7 +1201,7 @@ class getid3_lib } } - } else { + } elseif (!is_array($value)) { $newvaluelength = strlen(trim($value)); foreach ($ThisFileInfo['comments'][$tagname] as $existingkey => $existingvalue) { @@ -1222,8 +1213,9 @@ class getid3_lib } } - if (empty($ThisFileInfo['comments'][$tagname]) || !in_array(trim($value), $ThisFileInfo['comments'][$tagname])) { - $ThisFileInfo['comments'][$tagname][] = trim($value); + if (is_array($value) || empty($ThisFileInfo['comments'][$tagname]) || !in_array(trim($value), $ThisFileInfo['comments'][$tagname])) { + $value = (is_string($value) ? trim($value) : $value); + $ThisFileInfo['comments'][$tagname][] = $value; } } } @@ -1231,21 +1223,31 @@ class getid3_lib } // Copy to ['comments_html'] - foreach ($ThisFileInfo['comments'] as $field => $values) { - foreach ($values as $index => $value) { - $ThisFileInfo['comments_html'][$field][$index] = str_replace('�', '', getid3_lib::MultiByteCharString2HTML($value, $ThisFileInfo['encoding'])); - } - } + foreach ($ThisFileInfo['comments'] as $field => $values) { + if ($field == 'picture') { + // pictures can take up a lot of space, and we don't need multiple copies of them + // let there be a single copy in [comments][picture], and not elsewhere + continue; + } + foreach ($values as $index => $value) { + if (is_array($value)) { + $ThisFileInfo['comments_html'][$field][$index] = $value; + } else { + $ThisFileInfo['comments_html'][$field][$index] = str_replace('�', '', getid3_lib::MultiByteCharString2HTML($value, $ThisFileInfo['encoding'])); + } + } + } } + return true; } - function EmbeddedLookup($key, $begin, $end, $file, $name) { + static function EmbeddedLookup($key, $begin, $end, $file, $name) { // Cached static $cache; if (isset($cache[$file][$name])) { - return @$cache[$file][$name][$key]; + return (isset($cache[$file][$name][$key]) ? $cache[$file][$name][$key] : ''); } // Init @@ -1275,20 +1277,22 @@ class getid3_lib // METHOD B: cache all keys in this lookup - more memory but faster on next lookup of not-previously-looked-up key //$cache[$file][$name][substr($line, 0, $keylength)] = trim(substr($line, $keylength + 1)); - @list($ThisKey, $ThisValue) = explode("\t", $line, 2); + $explodedLine = explode("\t", $line, 2); + $ThisKey = (isset($explodedLine[0]) ? $explodedLine[0] : ''); + $ThisValue = (isset($explodedLine[1]) ? $explodedLine[1] : ''); $cache[$file][$name][$ThisKey] = trim($ThisValue); } // Close and return fclose($fp); - return @$cache[$file][$name][$key]; + return (isset($cache[$file][$name][$key]) ? $cache[$file][$name][$key] : ''); } - function IncludeDependency($filename, $sourcefile, $DieOnFailure=false) { + static function IncludeDependency($filename, $sourcefile, $DieOnFailure=false) { global $GETID3_ERRORARRAY; if (file_exists($filename)) { - if (@include_once($filename)) { + if (include_once($filename)) { return true; } else { $diemessage = basename($sourcefile).' depends on '.$filename.', which has errors'; @@ -1297,13 +1301,17 @@ class getid3_lib $diemessage = basename($sourcefile).' depends on '.$filename.', which is missing'; } if ($DieOnFailure) { - die($diemessage); + throw new Exception($diemessage); } else { $GETID3_ERRORARRAY[] = $diemessage; } return false; } + public static function trimNullByte($string) { + return trim($string, "\x00"); + } + } ?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/getid3.php b/3rdparty/getid3/getid3.php similarity index 52% rename from apps/media/getID3/getid3/getid3.php rename to 3rdparty/getid3/getid3.php index f9dcf706d0..e8a3f7e2de 100644 --- a/apps/media/getID3/getid3/getid3.php +++ b/3rdparty/getid3/getid3.php @@ -9,91 +9,164 @@ // /// ///////////////////////////////////////////////////////////////// -// Defines -define('GETID3_VERSION', '1.7.9-20090308'); -define('GETID3_FREAD_BUFFER_SIZE', 16384); // read buffer size in bytes +// attempt to define temp dir as something flexible but reliable +$temp_dir = ini_get('upload_tmp_dir'); +if ($temp_dir && (!is_dir($temp_dir) || !is_readable($temp_dir))) { + $temp_dir = ''; +} +if (!$temp_dir && function_exists('sys_get_temp_dir')) { + // PHP v5.2.1+ + // sys_get_temp_dir() may give inaccessible temp dir, e.g. with open_basedir on virtual hosts + $temp_dir = sys_get_temp_dir(); +} +$temp_dir = realpath($temp_dir); +$open_basedir = ini_get('open_basedir'); +if ($open_basedir) { + // e.g. "/var/www/vhosts/getid3.org/httpdocs/:/tmp/" + $temp_dir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $temp_dir); + $open_basedir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $open_basedir); + if (substr($temp_dir, -1, 1) != DIRECTORY_SEPARATOR) { + $temp_dir .= DIRECTORY_SEPARATOR; + } + $found_valid_tempdir = false; + $open_basedirs = explode(':', $open_basedir); + foreach ($open_basedirs as $basedir) { + if (substr($basedir, -1, 1) != DIRECTORY_SEPARATOR) { + $basedir .= DIRECTORY_SEPARATOR; + } + if (preg_match('#^'.preg_quote($basedir).'#', $temp_dir)) { + $found_valid_tempdir = true; + break; + } + } + if (!$found_valid_tempdir) { + $temp_dir = ''; + } + unset($open_basedirs, $found_valid_tempdir, $basedir); +} +if (!$temp_dir) { + $temp_dir = '*'; // invalid directory name should force tempnam() to use system default temp dir +} +// $temp_dir = '/something/else/'; // feel free to override temp dir here if it works better for your system +define('GETID3_TEMP_DIR', $temp_dir); +unset($open_basedir, $temp_dir); +// define a constant rather than looking up every time it is needed +if (!defined('GETID3_OS_ISWINDOWS')) { + if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') { + define('GETID3_OS_ISWINDOWS', true); + } else { + define('GETID3_OS_ISWINDOWS', false); + } +} + +// Get base path of getID3() - ONCE +if (!defined('GETID3_INCLUDEPATH')) { + foreach (get_included_files() as $key => $val) { + if (basename($val) == 'getid3.php') { + define('GETID3_INCLUDEPATH', dirname($val).DIRECTORY_SEPARATOR); + break; + } + } +} + +// End: Defines + class getID3 { // public: Settings - var $encoding = 'ISO-8859-1'; // CASE SENSITIVE! - i.e. (must be supported by iconv()) - // Examples: ISO-8859-1 UTF-8 UTF-16 UTF-16BE - - var $encoding_id3v1 = 'ISO-8859-1'; // Should always be 'ISO-8859-1', but some tags may be written in other encodings such as 'EUC-CN' - - var $tempdir = '*'; // default '*' should use system temp dir + public $encoding = 'UTF-8'; // CASE SENSITIVE! - i.e. (must be supported by iconv()). Examples: ISO-8859-1 UTF-8 UTF-16 UTF-16BE + public $encoding_id3v1 = 'ISO-8859-1'; // Should always be 'ISO-8859-1', but some tags may be written in other encodings such as 'EUC-CN' or 'CP1252' // public: Optional tag checks - disable for speed. - var $option_tag_id3v1 = true; // Read and process ID3v1 tags - var $option_tag_id3v2 = true; // Read and process ID3v2 tags - var $option_tag_lyrics3 = true; // Read and process Lyrics3 tags - var $option_tag_apetag = true; // Read and process APE tags - var $option_tags_process = true; // Copy tags to root key 'tags' and encode to $this->encoding - var $option_tags_html = true; // Copy tags to root key 'tags_html' properly translated from various encodings to HTML entities + public $option_tag_id3v1 = true; // Read and process ID3v1 tags + public $option_tag_id3v2 = true; // Read and process ID3v2 tags + public $option_tag_lyrics3 = true; // Read and process Lyrics3 tags + public $option_tag_apetag = true; // Read and process APE tags + public $option_tags_process = true; // Copy tags to root key 'tags' and encode to $this->encoding + public $option_tags_html = true; // Copy tags to root key 'tags_html' properly translated from various encodings to HTML entities // public: Optional tag/comment calucations - var $option_extra_info = true; // Calculate additional info such as bitrate, channelmode etc + public $option_extra_info = true; // Calculate additional info such as bitrate, channelmode etc + + // public: Optional handling of embedded attachments (e.g. images) + public $option_save_attachments = true; // defaults to true (ATTACHMENTS_INLINE) for backward compatibility // public: Optional calculations - var $option_md5_data = false; // Get MD5 sum of data part - slow - var $option_md5_data_source = false; // Use MD5 of source file if availble - only FLAC and OptimFROG - var $option_sha1_data = false; // Get SHA1 sum of data part - slow - var $option_max_2gb_check = true; // Check whether file is larger than 2 Gb and thus not supported by PHP + public $option_md5_data = false; // Get MD5 sum of data part - slow + public $option_md5_data_source = false; // Use MD5 of source file if availble - only FLAC and OptimFROG + public $option_sha1_data = false; // Get SHA1 sum of data part - slow + public $option_max_2gb_check = null; // Check whether file is larger than 2GB and thus not supported by 32-bit PHP (null: auto-detect based on PHP_INT_MAX) - // private - var $filename; + // public: Read buffer size in bytes + public $option_fread_buffer_size = 32768; + // Public variables + public $filename; // Filename of file being analysed. + public $fp; // Filepointer to file being analysed. + public $info; // Result array. + + // Protected variables + protected $startup_error = ''; + protected $startup_warning = ''; + protected $memory_limit = 0; + + const VERSION = '1.9.3-20111213'; + const FREAD_BUFFER_SIZE = 32768; + var $tempdir = GETID3_TEMP_DIR; + + const ATTACHMENTS_NONE = false; + const ATTACHMENTS_INLINE = true; // public: constructor - function getID3() - { + public function __construct() { - $this->startup_error = ''; - $this->startup_warning = ''; - - // Check for PHP version >= 4.2.0 - if (phpversion() < '4.2.0') { - $this->startup_error .= 'getID3() requires PHP v4.2.0 or higher - you are running v'.phpversion(); + // Check for PHP version + $required_php_version = '5.0.5'; + if (version_compare(PHP_VERSION, $required_php_version, '<')) { + $this->startup_error .= 'getID3() requires PHP v'.$required_php_version.' or higher - you are running v'.PHP_VERSION; + return false; } // Check memory - $memory_limit = ini_get('memory_limit'); - if (eregi('([0-9]+)M', $memory_limit, $matches)) { + $this->memory_limit = ini_get('memory_limit'); + if (preg_match('#([0-9]+)M#i', $this->memory_limit, $matches)) { // could be stored as "16M" rather than 16777216 for example - $memory_limit = $matches[1] * 1048576; + $this->memory_limit = $matches[1] * 1048576; + } elseif (preg_match('#([0-9]+)G#i', $this->memory_limit, $matches)) { // The 'G' modifier is available since PHP 5.1.0 + // could be stored as "2G" rather than 2147483648 for example + $this->memory_limit = $matches[1] * 1073741824; } - if ($memory_limit <= 0) { + if ($this->memory_limit <= 0) { // memory limits probably disabled - } elseif ($memory_limit <= 3145728) { - $this->startup_error .= 'PHP has less than 3MB available memory and will very likely run out. Increase memory_limit in php.ini'; - } elseif ($memory_limit <= 12582912) { - $this->startup_warning .= 'PHP has less than 12MB available memory and might run out if all modules are loaded. Increase memory_limit in php.ini'; + } elseif ($this->memory_limit <= 4194304) { + $this->startup_error .= 'PHP has less than 4MB available memory and will very likely run out. Increase memory_limit in php.ini'; + } elseif ($this->memory_limit <= 12582912) { + $this->startup_warning .= 'PHP has less than 12MB available memory and might run out if all modules are loaded. Increase memory_limit in php.ini'; } // Check safe_mode off - if ((bool) ini_get('safe_mode')) { - $this->warning('WARNING: Safe mode is on, shorten support disabled, md5data/sha1data for ogg vorbis disabled, ogg vorbos/flac tag writing disabled.'); + if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { + $this->warning('WARNING: Safe mode is on, shorten support disabled, md5data/sha1data for ogg vorbis disabled, ogg vorbos/flac tag writing disabled.'); } + if (intval(ini_get('mbstring.func_overload')) > 0) { + $this->warning('WARNING: php.ini contains "mbstring.func_overload = '.ini_get('mbstring.func_overload').'", this may break things.'); + } - // define a constant rather than looking up every time it is needed - if (!defined('GETID3_OS_ISWINDOWS')) { - if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') { - define('GETID3_OS_ISWINDOWS', true); - } else { - define('GETID3_OS_ISWINDOWS', false); + // Check for magic_quotes_runtime + if (function_exists('get_magic_quotes_runtime')) { + if (get_magic_quotes_runtime()) { + return $this->startup_error('magic_quotes_runtime must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_runtime(0) and set_magic_quotes_runtime(1).'); } } - // Get base path of getID3() - ONCE - if (!defined('GETID3_INCLUDEPATH')) { - foreach (get_included_files() as $key => $val) { - if (basename($val) == 'getid3.php') { - define('GETID3_INCLUDEPATH', dirname($val).DIRECTORY_SEPARATOR); - break; - } + // Check for magic_quotes_gpc + if (function_exists('magic_quotes_gpc')) { + if (get_magic_quotes_gpc()) { + return $this->startup_error('magic_quotes_gpc must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_gpc(0) and set_magic_quotes_gpc(1).'); } } @@ -102,36 +175,61 @@ class getID3 $this->startup_error .= 'getid3.lib.php is missing or corrupt'; } + if ($this->option_max_2gb_check === null) { + $this->option_max_2gb_check = (PHP_INT_MAX <= 2147483647); + } + // Needed for Windows only: // Define locations of helper applications for Shorten, VorbisComment, MetaFLAC // as well as other helper functions such as head, tail, md5sum, etc - // IMPORTANT: This path cannot have spaces in it. If neccesary, use the 8dot3 equivalent - // ie for "C:/Program Files/Apache/" put "C:/PROGRA~1/APACHE/" + // This path cannot contain spaces, but the below code will attempt to get the + // 8.3-equivalent path automatically // IMPORTANT: This path must include the trailing slash if (GETID3_OS_ISWINDOWS && !defined('GETID3_HELPERAPPSDIR')) { $helperappsdir = GETID3_INCLUDEPATH.'..'.DIRECTORY_SEPARATOR.'helperapps'; // must not have any space in this path if (!is_dir($helperappsdir)) { - $this->startup_error .= '"'.$helperappsdir.'" cannot be defined as GETID3_HELPERAPPSDIR because it does not exist'; + $this->startup_warning .= '"'.$helperappsdir.'" cannot be defined as GETID3_HELPERAPPSDIR because it does not exist'; } elseif (strpos(realpath($helperappsdir), ' ') !== false) { $DirPieces = explode(DIRECTORY_SEPARATOR, realpath($helperappsdir)); + $path_so_far = array(); foreach ($DirPieces as $key => $value) { - if ((strpos($value, '.') !== false) && (strpos($value, ' ') === false)) { - if (strpos($value, '.') > 8) { - $value = substr($value, 0, 6).'~1'; + if (strpos($value, ' ') !== false) { + if (!empty($path_so_far)) { + $commandline = 'dir /x '.escapeshellarg(implode(DIRECTORY_SEPARATOR, $path_so_far)); + $dir_listing = `$commandline`; + $lines = explode("\n", $dir_listing); + foreach ($lines as $line) { + $line = trim($line); + if (preg_match('#^([0-9/]{10}) +([0-9:]{4,5}( [AP]M)?) +(|[0-9,]+) +([^ ]{0,11}) +(.+)$#', $line, $matches)) { + list($dummy, $date, $time, $ampm, $filesize, $shortname, $filename) = $matches; + if ((strtoupper($filesize) == '') && (strtolower($filename) == strtolower($value))) { + $value = $shortname; + } + } + } + } else { + $this->startup_warning .= 'GETID3_HELPERAPPSDIR must not have any spaces in it - use 8dot3 naming convention if neccesary. You can run "dir /x" from the commandline to see the correct 8.3-style names.'; } - } elseif ((strpos($value, ' ') !== false) || strlen($value) > 8) { - $value = substr($value, 0, 6).'~1'; } - $DirPieces[$key] = strtoupper($value); + $path_so_far[] = $value; } - $this->startup_error .= 'GETID3_HELPERAPPSDIR must not have any spaces in it - use 8dot3 naming convention if neccesary (on this server that would be something like "'.implode(DIRECTORY_SEPARATOR, $DirPieces).'" - NOTE: this may or may not be the actual 8.3 equivalent of "'.$helperappsdir.'", please double-check). You can run "dir /x" from the commandline to see the correct 8.3-style names.'; + $helperappsdir = implode(DIRECTORY_SEPARATOR, $path_so_far); } - define('GETID3_HELPERAPPSDIR', realpath($helperappsdir).DIRECTORY_SEPARATOR); + define('GETID3_HELPERAPPSDIR', $helperappsdir.DIRECTORY_SEPARATOR); } + return true; + } + + public function version() { + return self::VERSION; + } + + public function fread_buffer_size() { + return $this->option_fread_buffer_size; } @@ -141,7 +239,6 @@ class getID3 return false; } foreach ($optArray as $opt => $val) { - //if (isset($this, $opt) === false) { if (isset($this->$opt) === false) { continue; } @@ -151,266 +248,256 @@ class getID3 } - // public: analyze file - replaces GetAllFileInfo() and GetTagOnly() - function analyze($filename) { + public function openfile($filename) { + try { + if (!empty($this->startup_error)) { + throw new getid3_exception($this->startup_error); + } + if (!empty($this->startup_warning)) { + $this->warning($this->startup_warning); + } - if (!empty($this->startup_error)) { - return $this->error($this->startup_error); - } - if (!empty($this->startup_warning)) { - $this->warning($this->startup_warning); - } + // init result array and set parameters + $this->filename = $filename; + $this->info = array(); + $this->info['GETID3_VERSION'] = $this->version(); + $this->info['php_memory_limit'] = $this->memory_limit; - // init result array and set parameters - $this->info = array(); - $this->info['GETID3_VERSION'] = GETID3_VERSION; + // remote files not supported + if (preg_match('/^(ht|f)tp:\/\//', $filename)) { + throw new getid3_exception('Remote files are not supported - please copy the file locally first'); + } - // Check encoding/iconv support - if (!function_exists('iconv') && !in_array($this->encoding, array('ISO-8859-1', 'UTF-8', 'UTF-16LE', 'UTF-16BE', 'UTF-16'))) { - $errormessage = 'iconv() support is needed for encodings other than ISO-8859-1, UTF-8, UTF-16LE, UTF16-BE, UTF-16. '; - if (GETID3_OS_ISWINDOWS) { - $errormessage .= 'PHP does not have iconv() support. Please enable php_iconv.dll in php.ini, and copy iconv.dll from c:/php/dlls to c:/windows/system32'; + $filename = str_replace('/', DIRECTORY_SEPARATOR, $filename); + $filename = preg_replace('#(.+)'.preg_quote(DIRECTORY_SEPARATOR).'{2,}#U', '\1'.DIRECTORY_SEPARATOR, $filename); + + // open local file + if (is_readable($filename) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) { + // great } else { - $errormessage .= 'PHP is not compiled with iconv() support. Please recompile with the --with-iconv switch'; + throw new getid3_exception('Could not open "'.$filename.'" (does not exist, or is not a file)'); } - return $this->error($errormessage); - } - // Disable magic_quotes_runtime, if neccesary - $old_magic_quotes_runtime = get_magic_quotes_runtime(); // store current setting of magic_quotes_runtime - if ($old_magic_quotes_runtime) { - set_magic_quotes_runtime(0); // turn off magic_quotes_runtime - if (get_magic_quotes_runtime()) { - return $this->error('Could not disable magic_quotes_runtime - getID3() cannot work properly with this setting enabled'); - } - } + $this->info['filesize'] = filesize($filename); + // set redundant parameters - might be needed in some include file + $this->info['filename'] = basename($filename); + $this->info['filepath'] = str_replace('\\', '/', realpath(dirname($filename))); + $this->info['filenamepath'] = $this->info['filepath'].'/'.$this->info['filename']; - // remote files not supported - if (preg_match('/^(ht|f)tp:\/\//', $filename)) { - return $this->error('Remote files are not supported in this version of getID3() - please copy the file locally first'); - } - $filename = str_replace('/', DIRECTORY_SEPARATOR, $filename); - $filename = preg_replace('#'.preg_quote(DIRECTORY_SEPARATOR).'{2,}#', DIRECTORY_SEPARATOR, $filename); - - // open local file - if (file_exists($filename) && ($fp = @fopen($filename, 'rb'))) { - // great - } else { - return $this->error('Could not open file "'.$filename.'"'); - } - - // set parameters - $this->info['filesize'] = filesize($filename); - - // option_max_2gb_check - if ($this->option_max_2gb_check) { - // PHP doesn't support integers larger than 31-bit (~2GB) - // filesize() simply returns (filesize % (pow(2, 32)), no matter the actual filesize - // ftell() returns 0 if seeking to the end is beyond the range of unsigned integer - fseek($fp, 0, SEEK_END); - if ((($this->info['filesize'] != 0) && (ftell($fp) == 0)) || - ($this->info['filesize'] < 0) || - (ftell($fp) < 0)) { - $real_filesize = false; - if (GETID3_OS_ISWINDOWS) { - $commandline = 'dir /-C "'.str_replace('/', DIRECTORY_SEPARATOR, $filename).'"'; - $dir_output = `$commandline`; - if (eregi('1 File\(s\)[ ]+([0-9]+) bytes', $dir_output, $matches)) { - $real_filesize = (float) $matches[1]; + // option_max_2gb_check + if ($this->option_max_2gb_check) { + // PHP (32-bit all, and 64-bit Windows) doesn't support integers larger than 2^31 (~2GB) + // filesize() simply returns (filesize % (pow(2, 32)), no matter the actual filesize + // ftell() returns 0 if seeking to the end is beyond the range of unsigned integer + $fseek = fseek($this->fp, 0, SEEK_END); + if (($fseek < 0) || (($this->info['filesize'] != 0) && (ftell($this->fp) == 0)) || + ($this->info['filesize'] < 0) || + (ftell($this->fp) < 0)) { + $real_filesize = false; + if (GETID3_OS_ISWINDOWS) { + $commandline = 'dir /-C "'.str_replace('/', DIRECTORY_SEPARATOR, $filename).'"'; + $dir_output = `$commandline`; + if (preg_match('#1 File\(s\)[ ]+([0-9]+) bytes#i', $dir_output, $matches)) { + $real_filesize = (float) $matches[1]; + } + } else { + $commandline = 'ls -o -g -G --time-style=long-iso '.escapeshellarg($filename); + $dir_output = `$commandline`; + if (preg_match('#([0-9]+) ([0-9]{4}-[0-9]{2}\-[0-9]{2} [0-9]{2}:[0-9]{2}) '.str_replace('#', '\\#', preg_quote($filename)).'$#', $dir_output, $matches)) { + $real_filesize = (float) $matches[1]; + } } - } else { - $commandline = 'ls -o -g -G --time-style=long-iso '.escapeshellarg($filename); - $dir_output = `$commandline`; - if (eregi('([0-9]+) ([0-9]{4}-[0-9]{2}\-[0-9]{2} [0-9]{2}:[0-9]{2}) '.preg_quote($filename).'$', $dir_output, $matches)) { - $real_filesize = (float) $matches[1]; + if ($real_filesize === false) { + unset($this->info['filesize']); + fclose($this->fp); + throw new getid3_exception('Unable to determine actual filesize. File is most likely larger than '.round(PHP_INT_MAX / 1073741824).'GB and is not supported by PHP.'); + } elseif (getid3_lib::intValueSupported($real_filesize)) { + unset($this->info['filesize']); + fclose($this->fp); + throw new getid3_exception('PHP seems to think the file is larger than '.round(PHP_INT_MAX / 1073741824).'GB, but filesystem reports it as '.number_format($real_filesize, 3).'GB, please report to info@getid3.org'); } + $this->info['filesize'] = $real_filesize; + $this->error('File is larger than '.round(PHP_INT_MAX / 1073741824).'GB (filesystem reports it as '.number_format($real_filesize, 3).'GB) and is not properly supported by PHP.'); + } + } + + // set more parameters + $this->info['avdataoffset'] = 0; + $this->info['avdataend'] = $this->info['filesize']; + $this->info['fileformat'] = ''; // filled in later + $this->info['audio']['dataformat'] = ''; // filled in later, unset if not used + $this->info['video']['dataformat'] = ''; // filled in later, unset if not used + $this->info['tags'] = array(); // filled in later, unset if not used + $this->info['error'] = array(); // filled in later, unset if not used + $this->info['warning'] = array(); // filled in later, unset if not used + $this->info['comments'] = array(); // filled in later, unset if not used + $this->info['encoding'] = $this->encoding; // required by id3v2 and iso modules - can be unset at the end if desired + + return true; + + } catch (Exception $e) { + $this->error($e->getMessage()); + } + return false; + } + + // public: analyze file + function analyze($filename) { + try { + if (!$this->openfile($filename)) { + return $this->info; + } + + // Handle tags + foreach (array('id3v2'=>'id3v2', 'id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) { + $option_tag = 'option_tag_'.$tag_name; + if ($this->$option_tag) { + $this->include_module('tag.'.$tag_name); + try { + $tag_class = 'getid3_'.$tag_name; + $tag = new $tag_class($this); + $tag->Analyze(); } - if ($real_filesize === false) { - unset($this->info['filesize']); - fclose($fp); - return $this->error('File is most likely larger than 2GB and is not supported by PHP'); - } elseif ($real_filesize < pow(2, 31)) { - unset($this->info['filesize']); - fclose($fp); - return $this->error('PHP seems to think the file is larger than 2GB, but filesystem reports it as '.number_format($real_filesize, 3).'GB, please report to info@getid3.org'); + catch (getid3_exception $e) { + throw $e; } - $this->info['filesize'] = $real_filesize; - $this->error('File is larger than 2GB (filesystem reports it as '.number_format($real_filesize, 3).'GB) and is not properly supported by PHP.'); + } } - } - - // set more parameters - $this->info['avdataoffset'] = 0; - $this->info['avdataend'] = $this->info['filesize']; - $this->info['fileformat'] = ''; // filled in later - $this->info['audio']['dataformat'] = ''; // filled in later, unset if not used - $this->info['video']['dataformat'] = ''; // filled in later, unset if not used - $this->info['tags'] = array(); // filled in later, unset if not used - $this->info['error'] = array(); // filled in later, unset if not used - $this->info['warning'] = array(); // filled in later, unset if not used - $this->info['comments'] = array(); // filled in later, unset if not used - $this->info['encoding'] = $this->encoding; // required by id3v2 and iso modules - can be unset at the end if desired - - // set redundant parameters - might be needed in some include file - $this->info['filename'] = basename($filename); - $this->info['filepath'] = str_replace('\\', '/', realpath(dirname($filename))); - $this->info['filenamepath'] = $this->info['filepath'].'/'.$this->info['filename']; - - - // handle ID3v2 tag - done first - already at beginning of file - // ID3v2 detection (even if not parsing) is always done otherwise fileformat is much harder to detect - if ($this->option_tag_id3v2) { - - $GETID3_ERRORARRAY = &$this->info['warning']; - if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, false)) { - $tag = new getid3_id3v2($fp, $this->info); - unset($tag); + if (isset($this->info['id3v2']['tag_offset_start'])) { + $this->info['avdataoffset'] = max($this->info['avdataoffset'], $this->info['id3v2']['tag_offset_end']); + } + foreach (array('id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) { + if (isset($this->info[$tag_key]['tag_offset_start'])) { + $this->info['avdataend'] = min($this->info['avdataend'], $this->info[$tag_key]['tag_offset_start']); + } } - } else { - - fseek($fp, 0, SEEK_SET); - $header = fread($fp, 10); - if (substr($header, 0, 3) == 'ID3' && strlen($header) == 10) { - $this->info['id3v2']['header'] = true; - $this->info['id3v2']['majorversion'] = ord($header{3}); - $this->info['id3v2']['minorversion'] = ord($header{4}); - $this->info['id3v2']['headerlength'] = getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length - - $this->info['id3v2']['tag_offset_start'] = 0; - $this->info['id3v2']['tag_offset_end'] = $this->info['id3v2']['tag_offset_start'] + $this->info['id3v2']['headerlength']; - $this->info['avdataoffset'] = $this->info['id3v2']['tag_offset_end']; + // ID3v2 detection (NOT parsing), even if ($this->option_tag_id3v2 == false) done to make fileformat easier + if (!$this->option_tag_id3v2) { + fseek($this->fp, 0, SEEK_SET); + $header = fread($this->fp, 10); + if ((substr($header, 0, 3) == 'ID3') && (strlen($header) == 10)) { + $this->info['id3v2']['header'] = true; + $this->info['id3v2']['majorversion'] = ord($header{3}); + $this->info['id3v2']['minorversion'] = ord($header{4}); + $this->info['avdataoffset'] += getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length + } } - } + // read 32 kb file data + fseek($this->fp, $this->info['avdataoffset'], SEEK_SET); + $formattest = fread($this->fp, 32774); + // determine format + $determined_format = $this->GetFileFormat($formattest, $filename); - // handle ID3v1 tag - if ($this->option_tag_id3v1) { - if (!@include_once(GETID3_INCLUDEPATH.'module.tag.id3v1.php')) { - return $this->error('module.tag.id3v1.php is missing - you may disable option_tag_id3v1.'); + // unable to determine file format + if (!$determined_format) { + fclose($this->fp); + return $this->error('unable to determine file format'); } - $tag = new getid3_id3v1($fp, $this->info); - unset($tag); - } - // handle APE tag - if ($this->option_tag_apetag) { - if (!@include_once(GETID3_INCLUDEPATH.'module.tag.apetag.php')) { - return $this->error('module.tag.apetag.php is missing - you may disable option_tag_apetag.'); + // check for illegal ID3 tags + if (isset($determined_format['fail_id3']) && (in_array('id3v1', $this->info['tags']) || in_array('id3v2', $this->info['tags']))) { + if ($determined_format['fail_id3'] === 'ERROR') { + fclose($this->fp); + return $this->error('ID3 tags not allowed on this file type.'); + } elseif ($determined_format['fail_id3'] === 'WARNING') { + $this->warning('ID3 tags not allowed on this file type.'); + } } - $tag = new getid3_apetag($fp, $this->info); - unset($tag); - } - // handle lyrics3 tag - if ($this->option_tag_lyrics3) { - if (!@include_once(GETID3_INCLUDEPATH.'module.tag.lyrics3.php')) { - return $this->error('module.tag.lyrics3.php is missing - you may disable option_tag_lyrics3.'); + // check for illegal APE tags + if (isset($determined_format['fail_ape']) && in_array('ape', $this->info['tags'])) { + if ($determined_format['fail_ape'] === 'ERROR') { + fclose($this->fp); + return $this->error('APE tags not allowed on this file type.'); + } elseif ($determined_format['fail_ape'] === 'WARNING') { + $this->warning('APE tags not allowed on this file type.'); + } } - $tag = new getid3_lyrics3($fp, $this->info); - unset($tag); - } - // read 32 kb file data - fseek($fp, $this->info['avdataoffset'], SEEK_SET); - $formattest = fread($fp, 32774); + // set mime type + $this->info['mime_type'] = $determined_format['mime_type']; - // determine format - $determined_format = $this->GetFileFormat($formattest, $filename); - - // unable to determine file format - if (!$determined_format) { - fclose($fp); - return $this->error('unable to determine file format'); - } - - // check for illegal ID3 tags - if (isset($determined_format['fail_id3']) && (in_array('id3v1', $this->info['tags']) || in_array('id3v2', $this->info['tags']))) { - if ($determined_format['fail_id3'] === 'ERROR') { - fclose($fp); - return $this->error('ID3 tags not allowed on this file type.'); - } elseif ($determined_format['fail_id3'] === 'WARNING') { - $this->info['warning'][] = 'ID3 tags not allowed on this file type.'; + // supported format signature pattern detected, but module deleted + if (!file_exists(GETID3_INCLUDEPATH.$determined_format['include'])) { + fclose($this->fp); + return $this->error('Format not supported, module "'.$determined_format['include'].'" was removed.'); } - } - // check for illegal APE tags - if (isset($determined_format['fail_ape']) && in_array('ape', $this->info['tags'])) { - if ($determined_format['fail_ape'] === 'ERROR') { - fclose($fp); - return $this->error('APE tags not allowed on this file type.'); - } elseif ($determined_format['fail_ape'] === 'WARNING') { - $this->info['warning'][] = 'APE tags not allowed on this file type.'; + // module requires iconv support + // Check encoding/iconv support + if (!empty($determined_format['iconv_req']) && !function_exists('iconv') && !in_array($this->encoding, array('ISO-8859-1', 'UTF-8', 'UTF-16LE', 'UTF-16BE', 'UTF-16'))) { + $errormessage = 'iconv() support is required for this module ('.$determined_format['include'].') for encodings other than ISO-8859-1, UTF-8, UTF-16LE, UTF16-BE, UTF-16. '; + if (GETID3_OS_ISWINDOWS) { + $errormessage .= 'PHP does not have iconv() support. Please enable php_iconv.dll in php.ini, and copy iconv.dll from c:/php/dlls to c:/windows/system32'; + } else { + $errormessage .= 'PHP is not compiled with iconv() support. Please recompile with the --with-iconv switch'; + } + return $this->error($errormessage); } - } - // set mime type - $this->info['mime_type'] = $determined_format['mime_type']; + // include module + include_once(GETID3_INCLUDEPATH.$determined_format['include']); - // supported format signature pattern detected, but module deleted - if (!file_exists(GETID3_INCLUDEPATH.$determined_format['include'])) { - fclose($fp); - return $this->error('Format not supported, module "'.$determined_format['include'].'" was removed.'); - } - - // module requires iconv support - if (!function_exists('iconv') && @$determined_format['iconv_req']) { - return $this->error('iconv support is required for this module ('.$determined_format['include'].').'); - } - - // include module - include_once(GETID3_INCLUDEPATH.$determined_format['include']); - - // instantiate module class - $class_name = 'getid3_'.$determined_format['module']; - if (!class_exists($class_name)) { - return $this->error('Format not supported, module "'.$determined_format['include'].'" is corrupt.'); - } - if (isset($determined_format['option'])) { - $class = new $class_name($fp, $this->info, $determined_format['option']); - } else { - $class = new $class_name($fp, $this->info); - } - unset($class); - - // close file - fclose($fp); - - // process all tags - copy to 'tags' and convert charsets - if ($this->option_tags_process) { - $this->HandleAllTags(); - } - - // perform more calculations - if ($this->option_extra_info) { - $this->ChannelsBitratePlaytimeCalculations(); - $this->CalculateCompressionRatioVideo(); - $this->CalculateCompressionRatioAudio(); - $this->CalculateReplayGain(); - $this->ProcessAudioStreams(); - } - - // get the MD5 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags - if ($this->option_md5_data) { - // do not cald md5_data if md5_data_source is present - set by flac only - future MPC/SV8 too - if (!$this->option_md5_data_source || empty($this->info['md5_data_source'])) { - $this->getHashdata('md5'); + // instantiate module class + $class_name = 'getid3_'.$determined_format['module']; + if (!class_exists($class_name)) { + return $this->error('Format not supported, module "'.$determined_format['include'].'" is corrupt.'); } + //if (isset($determined_format['option'])) { + // //$class = new $class_name($this->fp, $this->info, $determined_format['option']); + //} else { + //$class = new $class_name($this->fp, $this->info); + $class = new $class_name($this); + //} + + if (!empty($determined_format['set_inline_attachments'])) { + $class->inline_attachments = $this->option_save_attachments; + } + $class->Analyze(); + + unset($class); + + // close file + fclose($this->fp); + + // process all tags - copy to 'tags' and convert charsets + if ($this->option_tags_process) { + $this->HandleAllTags(); + } + + // perform more calculations + if ($this->option_extra_info) { + $this->ChannelsBitratePlaytimeCalculations(); + $this->CalculateCompressionRatioVideo(); + $this->CalculateCompressionRatioAudio(); + $this->CalculateReplayGain(); + $this->ProcessAudioStreams(); + } + + // get the MD5 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags + if ($this->option_md5_data) { + // do not cald md5_data if md5_data_source is present - set by flac only - future MPC/SV8 too + if (!$this->option_md5_data_source || empty($this->info['md5_data_source'])) { + $this->getHashdata('md5'); + } + } + + // get the SHA1 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags + if ($this->option_sha1_data) { + $this->getHashdata('sha1'); + } + + // remove undesired keys + $this->CleanUp(); + + } catch (Exception $e) { + $this->error('Caught exception: '.$e->getMessage()); } - // get the SHA1 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags - if ($this->option_sha1_data) { - $this->getHashdata('sha1'); - } - - // remove undesired keys - $this->CleanUp(); - - // restore magic_quotes_runtime setting - set_magic_quotes_runtime($old_magic_quotes_runtime); - // return info array return $this->info; } @@ -418,9 +505,10 @@ class getID3 // private: error handling function error($message) { - $this->CleanUp(); - + if (!isset($this->info['error'])) { + $this->info['error'] = array(); + } $this->info['error'][] = $message; return $this->info; } @@ -465,6 +553,19 @@ class getID3 unset($this->info['avdataend']); } } + + // remove possible duplicated identical entries + if (!empty($this->info['error'])) { + $this->info['error'] = array_values(array_unique($this->info['error'])); + } + if (!empty($this->info['warning'])) { + $this->info['warning'] = array_values(array_unique($this->info['warning'])); + } + + // remove "global variable" type keys + unset($this->info['php_memory_limit']); + + return true; } @@ -489,18 +590,24 @@ class getID3 'pattern' => '^ADIF', 'group' => 'audio', 'module' => 'aac', - 'option' => 'adif', 'mime_type' => 'application/octet-stream', 'fail_ape' => 'WARNING', ), + // AA - audio - Audible Audiobook + 'adts' => array( + 'pattern' => '^.{4}\x57\x90\x75\x36', + 'group' => 'audio', + 'module' => 'aa', + 'mime_type' => 'audio/audible ', + ), + // AAC - audio - Advanced Audio Coding (AAC) - ADTS format (very similar to MP3) 'adts' => array( 'pattern' => '^\xFF[\xF0-\xF1\xF8-\xF9]', 'group' => 'audio', 'module' => 'aac', - 'option' => 'adts', 'mime_type' => 'application/octet-stream', 'fail_ape' => 'WARNING', ), @@ -532,7 +639,7 @@ class getID3 // DSS - audio - Digital Speech Standard 'dss' => array( - 'pattern' => '^[\x02]dss', + 'pattern' => '^[\x02-\x03]dss', 'group' => 'audio', 'module' => 'dss', 'mime_type' => 'application/octet-stream', @@ -552,6 +659,7 @@ class getID3 'group' => 'audio', 'module' => 'flac', 'mime_type' => 'audio/x-flac', + 'set_inline_attachments' => true, ), // LA - audio - Lossless Audio (LA) @@ -601,7 +709,7 @@ class getID3 'pattern' => '^IMPM', 'group' => 'audio', 'module' => 'mod', - 'option' => 'it', + //'option' => 'it', 'mime_type' => 'audio/it', ), @@ -610,7 +718,7 @@ class getID3 'pattern' => '^Extended Module', 'group' => 'audio', 'module' => 'mod', - 'option' => 'xm', + //'option' => 'xm', 'mime_type' => 'audio/xm', ), @@ -619,7 +727,7 @@ class getID3 'pattern' => '^.{44}SCRM', 'group' => 'audio', 'module' => 'mod', - 'option' => 's3m', + //'option' => 's3m', 'mime_type' => 'audio/s3m', ), @@ -633,7 +741,7 @@ class getID3 // MP3 - audio - MPEG-audio Layer 3 (very similar to AAC-ADTS) 'mp3' => array( - 'pattern' => '^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\xEB]', + 'pattern' => '^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\x0B\x10-\x1B\x20-\x2B\x30-\x3B\x40-\x4B\x50-\x5B\x60-\x6B\x70-\x7B\x80-\x8B\x90-\x9B\xA0-\xAB\xB0-\xBB\xC0-\xCB\xD0-\xDB\xE0-\xEB\xF0-\xFB]', 'group' => 'audio', 'module' => 'mp3', 'mime_type' => 'audio/mpeg', @@ -731,6 +839,7 @@ class getID3 'group' => 'audio-video', 'module' => 'matroska', 'mime_type' => 'video/x-matroska', // may also be audio/x-matroska + 'set_inline_attachments' => true, ), // MPEG - audio/video - MPEG (Moving Pictures Experts Group) @@ -757,6 +866,7 @@ class getID3 'mime_type' => 'application/ogg', 'fail_id3' => 'WARNING', 'fail_ape' => 'WARNING', + 'set_inline_attachments' => true, ), // QT - audio/video - Quicktime @@ -847,9 +957,9 @@ class getID3 ), - // SVG - still image - Scalable Vector Graphics (SVG) + // SVG - still image - Scalable Vector Graphics (SVG) 'svg' => array( - 'pattern' => ' '( 'graphic', 'module' => 'svg', 'mime_type' => 'image/svg+xml', @@ -858,7 +968,7 @@ class getID3 ), - // TIFF - still image - Tagged Information File Format (TIFF) + // TIFF - still image - Tagged Information File Format (TIFF) 'tiff' => array( 'pattern' => '^(II\x2A\x00|MM\x00\x2A)', 'group' => 'graphic', @@ -869,6 +979,17 @@ class getID3 ), + // EFAX - still image - eFax (TIFF derivative) + 'bmp' => array( + 'pattern' => '^\xDC\xFE', + 'group' => 'graphic', + 'module' => 'efax', + 'mime_type' => 'image/efax', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // Data formats // ISO - data - International Standards Organization (ISO) CD-ROM Image @@ -935,10 +1056,10 @@ class getID3 // Misc other formats - // PAR2 - data - Parity Volume Set Specification 2.0 - 'par2' => array ( - 'pattern' => '^PAR2\x00PKT', - 'group' => 'misc', + // PAR2 - data - Parity Volume Set Specification 2.0 + 'par2' => array ( + 'pattern' => '^PAR2\x00PKT', + 'group' => 'misc', 'module' => 'par2', 'mime_type' => 'application/octet-stream', 'fail_id3' => 'ERROR', @@ -957,13 +1078,22 @@ class getID3 // MSOFFICE - data - ZIP compressed data 'msoffice' => array( - 'pattern' => '^\xD0\xCF\x11\xE0', // D0CF11E == DOCFILE == Microsoft Office Document + 'pattern' => '^\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1', // D0CF11E == DOCFILE == Microsoft Office Document 'group' => 'misc', 'module' => 'msoffice', 'mime_type' => 'application/octet-stream', 'fail_id3' => 'ERROR', 'fail_ape' => 'ERROR', ), + + // CUE - data - CUEsheet (index to single-file disc images) + 'cue' => array( + 'pattern' => '', // empty pattern means cannot be automatically detected, will fall through all other formats and match based on filename and very basic file contents + 'group' => 'misc', + 'module' => 'cue', + 'mime_type' => 'application/octet-stream', + ), + ); } @@ -980,23 +1110,30 @@ class getID3 // Identify file format - loop through $format_info and detect with reg expr foreach ($this->GetFileFormatArray() as $format_name => $info) { - // Using preg_match() instead of ereg() - much faster // The /s switch on preg_match() forces preg_match() NOT to treat // newline (0x0A) characters as special chars but do a binary match - if (preg_match('/'.$info['pattern'].'/s', $filedata)) { + if (!empty($info['pattern']) && preg_match('#'.$info['pattern'].'#s', $filedata)) { $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; return $info; } } - if (preg_match('/\.mp[123a]$/i', $filename)) { + if (preg_match('#\.mp[123a]$#i', $filename)) { // Too many mp3 encoders on the market put gabage in front of mpeg files // use assume format on these if format detection failed $GetFileFormatArray = $this->GetFileFormatArray(); $info = $GetFileFormatArray['mp3']; $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; return $info; + } elseif (preg_match('/\.cue$/i', $filename) && preg_match('#FILE "[^"]+" (BINARY|MOTOROLA|AIFF|WAVE|MP3)#', $filedata)) { + // there's not really a useful consistent "magic" at the beginning of .cue files to identify them + // so until I think of something better, just go by filename if all other format checks fail + // and verify there's at least one instance of "TRACK xx AUDIO" in the file + $GetFileFormatArray = $this->GetFileFormatArray(); + $info = $GetFileFormatArray['cue']; + $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; + return $info; } return false; @@ -1039,7 +1176,7 @@ class getID3 'ogg' => array('vorbiscomment' , 'UTF-8'), 'png' => array('png' , 'UTF-8'), 'tiff' => array('tiff' , 'ISO-8859-1'), - 'quicktime' => array('quicktime' , 'ISO-8859-1'), + 'quicktime' => array('quicktime' , 'UTF-8'), 'real' => array('real' , 'ISO-8859-1'), 'vqf' => array('vqf' , 'ISO-8859-1'), 'zip' => array('zip' , 'ISO-8859-1'), @@ -1047,11 +1184,13 @@ class getID3 'lyrics3' => array('lyrics3' , 'ISO-8859-1'), 'id3v1' => array('id3v1' , $this->encoding_id3v1), 'id3v2' => array('id3v2' , 'UTF-8'), // not according to the specs (every frame can have a different encoding), but getID3() force-converts all encodings to UTF-8 - 'ape' => array('ape' , 'UTF-8') + 'ape' => array('ape' , 'UTF-8'), + 'cue' => array('cue' , 'ISO-8859-1'), + 'matroska' => array('matroska' , 'UTF-8'), ); } - // loop thru comments array + // loop through comments array foreach ($tags as $comment_name => $tagname_encoding_array) { list($tag_name, $encoding) = $tagname_encoding_array; @@ -1065,8 +1204,11 @@ class getID3 foreach ($this->info[$comment_name]['comments'] as $tag_key => $valuearray) { foreach ($valuearray as $key => $value) { - if (strlen(trim($value)) > 0) { - $this->info['tags'][trim($tag_name)][trim($tag_key)][] = $value; // do not trim!! Unicode characters will get mangled if trailing nulls are removed! + if (is_string($value)) { + $value = trim($value, " \r\n\t"); // do not trim nulls from $value!! Unicode characters will get mangled if trailing nulls are removed! + } + if ($value) { + $this->info['tags'][trim($tag_name)][trim($tag_key)][] = $value; } } } @@ -1081,7 +1223,7 @@ class getID3 foreach ($valuearray as $key => $value) { if (is_string($value)) { //$this->info['tags_html'][$tag_name][$tag_key][$key] = getid3_lib::MultiByteCharString2HTML($value, $encoding); - $this->info['tags_html'][$tag_name][$tag_key][$key] = str_replace('�', '', getid3_lib::MultiByteCharString2HTML($value, $encoding)); + $this->info['tags_html'][$tag_name][$tag_key][$key] = str_replace('�', '', trim(getid3_lib::MultiByteCharString2HTML($value, $encoding))); } else { $this->info['tags_html'][$tag_name][$tag_key][$key] = $value; } @@ -1093,6 +1235,51 @@ class getID3 } } + + // pictures can take up a lot of space, and we don't need multiple copies of them + // let there be a single copy in [comments][picture], and not elsewhere + if (!empty($this->info['tags'])) { + $unset_keys = array('tags', 'tags_html'); + foreach ($this->info['tags'] as $tagtype => $tagarray) { + foreach ($tagarray as $tagname => $tagdata) { + if ($tagname == 'picture') { + foreach ($tagdata as $key => $tagarray) { + $this->info['comments']['picture'][] = $tagarray; + if (isset($tagarray['data']) && isset($tagarray['image_mime'])) { + if (isset($this->info['tags'][$tagtype][$tagname][$key])) { + unset($this->info['tags'][$tagtype][$tagname][$key]); + } + if (isset($this->info['tags_html'][$tagtype][$tagname][$key])) { + unset($this->info['tags_html'][$tagtype][$tagname][$key]); + } + } + } + } + } + foreach ($unset_keys as $unset_key) { + // remove possible empty keys from (e.g. [tags][id3v2][picture]) + if (empty($this->info[$unset_key][$tagtype]['picture'])) { + unset($this->info[$unset_key][$tagtype]['picture']); + } + if (empty($this->info[$unset_key][$tagtype])) { + unset($this->info[$unset_key][$tagtype]); + } + if (empty($this->info[$unset_key])) { + unset($this->info[$unset_key]); + } + } + // remove duplicate copy of picture data from (e.g. [id3v2][comments][picture]) + if (isset($this->info[$tagtype]['comments']['picture'])) { + unset($this->info[$tagtype]['comments']['picture']); + } + if (empty($this->info[$tagtype]['comments'])) { + unset($this->info[$tagtype]['comments']); + } + if (empty($this->info[$tagtype])) { + unset($this->info[$tagtype]); + } + } + } return true; } @@ -1108,7 +1295,7 @@ class getID3 break; } - if ((@$this->info['fileformat'] == 'ogg') && (@$this->info['audio']['dataformat'] == 'vorbis')) { + if (!empty($this->info['fileformat']) && !empty($this->info['dataformat']) && ($this->info['fileformat'] == 'ogg') && ($this->info['audio']['dataformat'] == 'vorbis')) { // We cannot get an identical md5_data value for Ogg files where the comments // span more than 1 Ogg page (compared to the same audio data with smaller @@ -1128,10 +1315,10 @@ class getID3 // page sequence numbers likely happens for OggSpeex and OggFLAC as well, but // currently vorbiscomment only works on OggVorbis files. - if ((bool) ini_get('safe_mode')) { + if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { - $this->info['warning'][] = 'Failed making system call to vorbiscomment.exe - '.$algorithm.'_data is incorrect - error returned: PHP running in Safe Mode (backtick operator not available)'; - $this->info[$algorithm.'_data'] = false; + $this->warning('Failed making system call to vorbiscomment.exe - '.$algorithm.'_data is incorrect - error returned: PHP running in Safe Mode (backtick operator not available)'); + $this->info[$algorithm.'_data'] = false; } else { @@ -1139,12 +1326,11 @@ class getID3 $old_abort = ignore_user_abort(true); // Create empty file - $empty = tempnam('*', 'getID3'); + $empty = tempnam(GETID3_TEMP_DIR, 'getID3'); touch($empty); - // Use vorbiscomment to make temp file without comments - $temp = tempnam('*', 'getID3'); + $temp = tempnam(GETID3_TEMP_DIR, 'getID3'); $file = $this->info['filenamepath']; if (GETID3_OS_ISWINDOWS) { @@ -1178,11 +1364,11 @@ class getID3 // Get hash of newly created file switch ($algorithm) { case 'md5': - $this->info[$algorithm.'_data'] = getid3_lib::md5_file($temp); + $this->info[$algorithm.'_data'] = md5_file($temp); break; case 'sha1': - $this->info[$algorithm.'_data'] = getid3_lib::sha1_file($temp); + $this->info[$algorithm.'_data'] = sha1_file($temp); break; } } @@ -1208,11 +1394,11 @@ class getID3 // get hash from whole file switch ($algorithm) { case 'md5': - $this->info[$algorithm.'_data'] = getid3_lib::md5_file($this->info['filenamepath']); + $this->info[$algorithm.'_data'] = md5_file($this->info['filenamepath']); break; case 'sha1': - $this->info[$algorithm.'_data'] = getid3_lib::sha1_file($this->info['filenamepath']); + $this->info[$algorithm.'_data'] = sha1_file($this->info['filenamepath']); break; } } @@ -1225,9 +1411,11 @@ class getID3 function ChannelsBitratePlaytimeCalculations() { // set channelmode on audio - if (@$this->info['audio']['channels'] == '1') { + if (!empty($this->info['audio']['channelmode']) || !isset($this->info['audio']['channels'])) { + // ignore + } elseif ($this->info['audio']['channels'] == 1) { $this->info['audio']['channelmode'] = 'mono'; - } elseif (@$this->info['audio']['channels'] == '2') { + } elseif ($this->info['audio']['channels'] == 2) { $this->info['audio']['channelmode'] = 'stereo'; } @@ -1268,11 +1456,6 @@ class getID3 if (!isset($this->info['bitrate']) && !empty($this->info['playtime_seconds'])) { $this->info['bitrate'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']; } -//echo '
';
-//var_dump($this->info['bitrate']);
-//var_dump($this->info['audio']['bitrate']);
-//var_dump($this->info['video']['bitrate']);
-//echo '
'; if (isset($this->info['bitrate']) && empty($this->info['audio']['bitrate']) && empty($this->info['video']['bitrate'])) { if (isset($this->info['audio']['dataformat']) && empty($this->info['video']['resolution_x'])) { // audio only @@ -1357,7 +1540,9 @@ class getID3 function CalculateReplayGain() { if (isset($this->info['replay_gain'])) { - $this->info['replay_gain']['reference_volume'] = 89; + if (!isset($this->info['replay_gain']['reference_volume'])) { + $this->info['replay_gain']['reference_volume'] = (double) 89.0; + } if (isset($this->info['replay_gain']['track']['adjustment'])) { $this->info['replay_gain']['track']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['track']['adjustment']; } @@ -1392,6 +1577,168 @@ class getID3 return tempnam($this->tempdir, 'gI3'); } + + public function saveAttachment(&$ThisFileInfoIndex, $filename, $offset, $length) { + try { + if (!getid3_lib::intValueSupported($offset + $length)) { + throw new Exception('cannot extract attachment, it extends beyond the '.round(PHP_INT_MAX / 1073741824).'GB limit'); + } + + // do not extract at all + if ($this->option_save_attachments === getID3::ATTACHMENTS_NONE) { + + unset($ThisFileInfoIndex); // do not set any + + // extract to return array + } elseif ($this->option_save_attachments === getID3::ATTACHMENTS_INLINE) { + + // get whole data in one pass, till it is anyway stored in memory + $ThisFileInfoIndex = file_get_contents($this->info['filenamepath'], false, null, $offset, $length); + if (($ThisFileInfoIndex === false) || (strlen($ThisFileInfoIndex) != $length)) { // verify + throw new Exception('failed to read attachment data'); + } + + // assume directory path is given + } else { + + $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->option_save_attachments), DIRECTORY_SEPARATOR); + // check supplied directory + if (!is_dir($dir) || !is_writable($dir)) { + throw new Exception('getID3::saveAttachment() -- supplied path ('.$dir.') does not exist, or is not writable'); + } + + // set up destination path + $dest = $dir.DIRECTORY_SEPARATOR.$filename; + + // optimize speed if read buffer size is configured to be large enough + // here stream_copy_to_stream() may also be used. need to do speed-compare tests + if ($length <= $this->fread_buffer_size()) { + $data = file_get_contents($this->info['filenamepath'], false, null, $offset, $length); + if (($data === false) || (strlen($data) != $length)) { // verify + throw new Exception('failed to read attachment data'); + } + if (!file_put_contents($dest, $data)) { + throw new Exception('failed to create file '.$dest); + } + } else { + // optimization not available - copy data in loop + // here stream_copy_to_stream() shouldn't be used because it's internal read buffer may be larger than ours! + getid3_lib::CopyFileParts($this->info['filenamepath'], $dest, $offset, $length); + } + $ThisFileInfoIndex = $dest; + } + + } catch (Exception $e) { + + unset($ThisFileInfoIndex); // do not set any is case of error + $this->warning('Failed to extract attachment '.$filename.': '.$e->getMessage()); + return false; + + } + return true; + } + + + public function include_module($name) { + //if (!file_exists($this->include_path.'module.'.$name.'.php')) { + if (!file_exists(GETID3_INCLUDEPATH.'module.'.$name.'.php')) { + throw new getid3_exception('Required module.'.$name.'.php is missing.'); + } + include_once(GETID3_INCLUDEPATH.'module.'.$name.'.php'); + return true; + } + +} + + +abstract class getid3_handler +{ + protected $getid3; // pointer + + protected $data_string_flag = false; // analyzing filepointer or string + protected $data_string; // string to analyze + protected $data_string_position = 0; // seek position in string + + + public function __construct(getID3 $getid3) { + $this->getid3 = $getid3; + } + + + // Analyze from file pointer + abstract public function Analyze(); + + + // Analyze from string instead + public function AnalyzeString(&$string) { + // Enter string mode + $this->data_string_flag = true; + $this->data_string = $string; + + // Save info + $saved_avdataoffset = $this->getid3->info['avdataoffset']; + $saved_avdataend = $this->getid3->info['avdataend']; + $saved_filesize = $this->getid3->info['filesize']; + + // Reset some info + $this->getid3->info['avdataoffset'] = 0; + $this->getid3->info['avdataend'] = $this->getid3->info['filesize'] = strlen($string); + + // Analyze + $this->Analyze(); + + // Restore some info + $this->getid3->info['avdataoffset'] = $saved_avdataoffset; + $this->getid3->info['avdataend'] = $saved_avdataend; + $this->getid3->info['filesize'] = $saved_filesize; + + // Exit string mode + $this->data_string_flag = false; + } + + + protected function ftell() { + if ($this->data_string_flag) { + return $this->data_string_position; + } + return ftell($this->getid3->fp); + } + + + protected function fread($bytes) { + if ($this->data_string_flag) { + $this->data_string_position += $bytes; + return substr($this->data_string, $this->data_string_position - $bytes, $bytes); + } + return fread($this->getid3->fp, $bytes); + } + + + protected function fseek($bytes, $whence = SEEK_SET) { + if ($this->data_string_flag) { + switch ($whence) { + case SEEK_SET: + $this->data_string_position = $bytes; + return; + + case SEEK_CUR: + $this->data_string_position += $bytes; + return; + + case SEEK_END: + $this->data_string_position = strlen($this->data_string) + $bytes; + return; + } + } + return fseek($this->getid3->fp, $bytes, $whence); + } + +} + + +class getid3_exception extends Exception +{ + public $message; } ?> \ No newline at end of file diff --git a/apps/media/getID3/license.txt b/3rdparty/getid3/license.txt similarity index 100% rename from apps/media/getID3/license.txt rename to 3rdparty/getid3/license.txt diff --git a/apps/media/getID3/getid3/module.archive.gzip.php b/3rdparty/getid3/module.archive.gzip.php similarity index 62% rename from apps/media/getID3/getid3/module.archive.gzip.php rename to 3rdparty/getid3/module.archive.gzip.php index 7e9376f37b..c30052eda4 100644 --- a/apps/media/getID3/getid3/module.archive.gzip.php +++ b/3rdparty/getid3/module.archive.gzip.php @@ -19,21 +19,28 @@ ///////////////////////////////////////////////////////////////// -class getid3_gzip { +class getid3_gzip extends getid3_handler { // public: Optional file list - disable for speed. var $option_gzip_parse_contents = false; // decode gzipped files, if possible, and parse recursively (.tar.gz for example) - function getid3_gzip(&$fd, &$ThisFileInfo) { - $ThisFileInfo['fileformat'] = 'gzip'; + 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 | //+---+---+---+---+---+---+---+---+---+---+ - @fseek($fd, 0); - $buffer = @fread($fd, $ThisFileInfo['filesize']); + + 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) { @@ -59,7 +66,7 @@ class getid3_gzip { } } - $ThisFileInfo['gzip']['files'] = array(); + $info['gzip']['files'] = array(); $fpointer = 0; $idx = 0; @@ -67,29 +74,29 @@ class getid3_gzip { if (strlen($arr_members[$i]) == 0) { continue; } - $thisThisFileInfo = &$ThisFileInfo['gzip']['member_header'][++$idx]; + $thisInfo = &$info['gzip']['member_header'][++$idx]; $buff = "\x1F\x8B\x08".$arr_members[$i]; $attr = unpack($unpack_header, substr($buff, 0, $start_length)); - $thisThisFileInfo['filemtime'] = getid3_lib::LittleEndian2Int($attr['mtime']); - $thisThisFileInfo['raw']['id1'] = ord($attr['cmethod']); - $thisThisFileInfo['raw']['id2'] = ord($attr['cmethod']); - $thisThisFileInfo['raw']['cmethod'] = ord($attr['cmethod']); - $thisThisFileInfo['raw']['os'] = ord($attr['os']); - $thisThisFileInfo['raw']['xflags'] = ord($attr['xflags']); - $thisThisFileInfo['raw']['flags'] = ord($attr['flags']); + $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']); - $thisThisFileInfo['flags']['crc16'] = (bool) ($thisThisFileInfo['raw']['flags'] & 0x02); - $thisThisFileInfo['flags']['extra'] = (bool) ($thisThisFileInfo['raw']['flags'] & 0x04); - $thisThisFileInfo['flags']['filename'] = (bool) ($thisThisFileInfo['raw']['flags'] & 0x08); - $thisThisFileInfo['flags']['comment'] = (bool) ($thisThisFileInfo['raw']['flags'] & 0x10); + $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); - $thisThisFileInfo['compression'] = $this->get_xflag_type($thisThisFileInfo['raw']['xflags']); + $thisInfo['compression'] = $this->get_xflag_type($thisInfo['raw']['xflags']); - $thisThisFileInfo['os'] = $this->get_os_type($thisThisFileInfo['raw']['os']); - if (!$thisThisFileInfo['os']) { - $ThisFileInfo['error'][] = 'Read error on gzip file'; + $thisInfo['os'] = $this->get_os_type($thisInfo['raw']['os']); + if (!$thisInfo['os']) { + $info['error'][] = 'Read error on gzip file'; return false; } @@ -99,12 +106,12 @@ class getid3_gzip { //+---+---+=================================+ //| XLEN |...XLEN bytes of "extra field"...| //+---+---+=================================+ - if ($thisThisFileInfo['flags']['extra']) { + if ($thisInfo['flags']['extra']) { $w_xlen = substr($buff, $fpointer, 2); $xlen = getid3_lib::LittleEndian2Int($w_xlen); $fpointer += 2; - $thisThisFileInfo['raw']['xfield'] = substr($buff, $fpointer, $xlen); + $thisInfo['raw']['xfield'] = substr($buff, $fpointer, $xlen); // Extra SubFields //+---+---+---+---+==================================+ //|SI1|SI2| LEN |... LEN bytes of subfield data ...| @@ -133,14 +140,14 @@ class getid3_gzip { //|...original file name, zero-terminated...| //+=========================================+ // GZIP files may have only one file, with no filename, so assume original filename is current filename without .gz - $thisThisFileInfo['filename'] = eregi_replace('.gz$', '', $ThisFileInfo['filename']); - if ($thisThisFileInfo['flags']['filename']) { + $thisInfo['filename'] = preg_replace('#\.gz$#i', '', $info['filename']); + if ($thisInfo['flags']['filename']) { while (true) { if (ord($buff[$fpointer]) == 0) { $fpointer++; break; } - $thisThisFileInfo['filename'] .= $buff[$fpointer]; + $thisInfo['filename'] .= $buff[$fpointer]; $fpointer++; } } @@ -148,13 +155,13 @@ class getid3_gzip { //+===================================+ //|...file comment, zero-terminated...| //+===================================+ - if ($thisThisFileInfo['flags']['comment']) { + if ($thisInfo['flags']['comment']) { while (true) { if (ord($buff[$fpointer]) == 0) { $fpointer++; break; } - $thisThisFileInfo['comment'] .= $buff[$fpointer]; + $thisInfo['comment'] .= $buff[$fpointer]; $fpointer++; } } @@ -162,21 +169,21 @@ class getid3_gzip { //+---+---+ //| CRC16 | //+---+---+ - if ($thisThisFileInfo['flags']['crc16']) { + if ($thisInfo['flags']['crc16']) { $w_crc = substr($buff, $fpointer, 2); - $thisThisFileInfo['crc16'] = getid3_lib::LittleEndian2Int($w_crc); + $thisInfo['crc16'] = getid3_lib::LittleEndian2Int($w_crc); $fpointer += 2; } // bit 0 - FLG.FTEXT - //if ($thisThisFileInfo['raw']['flags'] & 0x01) { + //if ($thisInfo['raw']['flags'] & 0x01) { // Ignored... //} // bits 5, 6, 7 - reserved - $thisThisFileInfo['crc32'] = getid3_lib::LittleEndian2Int(substr($buff, strlen($buff) - 8, 4)); - $thisThisFileInfo['filesize'] = getid3_lib::LittleEndian2Int(substr($buff, strlen($buff) - 4)); + $thisInfo['crc32'] = getid3_lib::LittleEndian2Int(substr($buff, strlen($buff) - 8, 4)); + $thisInfo['filesize'] = getid3_lib::LittleEndian2Int(substr($buff, strlen($buff) - 4)); - $ThisFileInfo['gzip']['files'] = getid3_lib::array_merge_clobber($ThisFileInfo['gzip']['files'], getid3_lib::CreateDeepArray($thisThisFileInfo['filename'], '/', $thisThisFileInfo['filesize'])); + $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 @@ -190,44 +197,46 @@ class getid3_gzip { $inflated = gzinflate($cdata); // Calculate CRC32 for inflated content - $thisThisFileInfo['crc32_valid'] = (bool) (sprintf('%u', crc32($inflated)) == $thisThisFileInfo['crc32']); + $thisInfo['crc32_valid'] = (bool) (sprintf('%u', crc32($inflated)) == $thisInfo['crc32']); // determine format $formattest = substr($inflated, 0, 32774); - $newgetID3 = new getID3(); - $determined_format = $newgetID3->GetFileFormat($formattest); - unset($newgetID3); + $getid3_temp = new getID3(); + $determined_format = $getid3_temp->GetFileFormat($formattest); + unset($getid3_temp); - // file format is determined - switch (@$determined_format['module']) { - case 'tar': + // 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')) === false) { + 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 - $ThisFileInfo['error'][] = 'Unable to create temp file to parse TAR inside GZIP file'; + $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); - rewind($fp_temp_tar); - $getid3_tar = new getid3_tar($fp_temp_tar, $dummy); - $ThisFileInfo['gzip']['member_header'][$idx]['tar'] = $dummy['tar']; - unset($dummy); - unset($getid3_tar); 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 { - $ThisFileInfo['error'][] = 'Unable to fopen() temp file to parse TAR inside GZIP file'; + $info['error'][] = 'Unable to fopen() temp file to parse TAR inside GZIP file'; break; } } break; - case '': - default: - // unknown or unhandled format - break; + case '': + default: + // unknown or unhandled format + break; } } } @@ -254,7 +263,7 @@ class getid3_gzip { '13' => 'Acorn RISCOS', '255' => 'unknown' ); - return @$os_type[$key]; + return (isset($os_type[$key]) ? $os_type[$key] : ''); } // Converts the eXtra FLags @@ -264,7 +273,7 @@ class getid3_gzip { '2' => 'maximum compression', '4' => 'fastest algorithm' ); - return @$xflag_type[$key]; + return (isset($xflag_type[$key]) ? $xflag_type[$key] : ''); } } diff --git a/apps/media/getID3/getid3/module.archive.rar.php b/3rdparty/getid3/module.archive.rar.php similarity index 62% rename from apps/media/getID3/getid3/module.archive.rar.php rename to 3rdparty/getid3/module.archive.rar.php index 59820b2fad..4f5d46f837 100644 --- a/apps/media/getID3/getid3/module.archive.rar.php +++ b/3rdparty/getid3/module.archive.rar.php @@ -14,33 +14,34 @@ ///////////////////////////////////////////////////////////////// -class getid3_rar +class getid3_rar extends getid3_handler { var $option_use_rar_extension = false; - function getid3_rar(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - $ThisFileInfo['fileformat'] = 'rar'; + $info['fileformat'] = 'rar'; if ($this->option_use_rar_extension === true) { if (function_exists('rar_open')) { - if ($rp = rar_open($ThisFileInfo['filename'])) { - $ThisFileInfo['rar']['files'] = array(); + if ($rp = rar_open($info['filenamepath'])) { + $info['rar']['files'] = array(); $entries = rar_list($rp); foreach ($entries as $entry) { - $ThisFileInfo['rar']['files'] = getid3_lib::array_merge_clobber($ThisFileInfo['rar']['files'], getid3_lib::CreateDeepArray($entry->getName(), '/', $entry->getUnpackedSize())); + $info['rar']['files'] = getid3_lib::array_merge_clobber($info['rar']['files'], getid3_lib::CreateDeepArray($entry->getName(), '/', $entry->getUnpackedSize())); } rar_close($rp); return true; } else { - $ThisFileInfo['error'][] = 'failed to rar_open('.$ThisFileInfo['filename'].')'; + $info['error'][] = 'failed to rar_open('.$info['filename'].')'; } } else { - $ThisFileInfo['error'][] = 'RAR support does not appear to be available in this PHP installation'; + $info['error'][] = 'RAR support does not appear to be available in this PHP installation'; } } else { - $ThisFileInfo['error'][] = 'PHP-RAR processing has been disabled (set $getid3_rar->option_use_rar_extension=true to enable)'; + $info['error'][] = 'PHP-RAR processing has been disabled (set $getid3_rar->option_use_rar_extension=true to enable)'; } return false; diff --git a/apps/media/getID3/getid3/module.archive.szip.php b/3rdparty/getid3/module.archive.szip.php similarity index 75% rename from apps/media/getID3/getid3/module.archive.szip.php rename to 3rdparty/getid3/module.archive.szip.php index 2513c85c7b..3be6253263 100644 --- a/apps/media/getID3/getid3/module.archive.szip.php +++ b/3rdparty/getid3/module.archive.szip.php @@ -14,36 +14,35 @@ ///////////////////////////////////////////////////////////////// -class getid3_szip +class getid3_szip extends getid3_handler { - function getid3_szip(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $SZIPHeader = fread($fd, 6); - if (substr($SZIPHeader, 0, 4) != 'SZ'."\x0A\x04") { - $ThisFileInfo['error'][] = 'Expecting "SZ[x0A][x04]" at offset '.$ThisFileInfo['avdataoffset'].', found "'.substr($SZIPHeader, 0, 4).'"'; + 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)); - $ThisFileInfo['fileformat'] = 'szip'; - - $ThisFileInfo['szip']['major_version'] = getid3_lib::BigEndian2Int(substr($SZIPHeader, 4, 1)); - $ThisFileInfo['szip']['minor_version'] = getid3_lib::BigEndian2Int(substr($SZIPHeader, 5, 1)); - - while (!feof($fd)) { - $NextBlockID = fread($fd, 2); + 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($fd, 4, SEEK_CUR); + fseek($this->getid3->fp, 4, SEEK_CUR); break; case 'BH': - $BHheaderbytes = getid3_lib::BigEndian2Int(fread($fd, 3)); - $BHheaderdata = fread($fd, $BHheaderbytes); + $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) @@ -79,7 +78,7 @@ class getid3_szip $BHdataArray['access_time'] = getid3_lib::BigEndian2Int(substr($BHheaderdata, $BHheaderoffset, 4)); $BHheaderoffset += 4; - $ThisFileInfo['szip']['BH'][] = $BHdataArray; + $info['szip']['BH'][] = $BHdataArray; } break; diff --git a/apps/media/getID3/getid3/module.archive.tar.php b/3rdparty/getid3/module.archive.tar.php similarity index 71% rename from apps/media/getID3/getid3/module.archive.tar.php rename to 3rdparty/getid3/module.archive.tar.php index aa4390fc9f..94d3203989 100644 --- a/apps/media/getID3/getid3/module.archive.tar.php +++ b/3rdparty/getid3/module.archive.tar.php @@ -19,18 +19,21 @@ ///////////////////////////////////////////////////////////////// -class getid3_tar { +class getid3_tar extends getid3_handler +{ - function getid3_tar(&$fd, &$ThisFileInfo) { - $ThisFileInfo['fileformat'] = 'tar'; - $ThisFileInfo['tar']['files'] = array(); + 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($fd, 0); - while (!feof($fd)) { - $buffer = fread($fd, 512); + fseek($this->getid3->fp, 0); + while (!feof($this->getid3->fp)) { + $buffer = fread($this->getid3->fp, 512); if (strlen($buffer) < 512) { break; } @@ -47,22 +50,22 @@ class getid3_tar { $checksum += ord($buffer{$i}); } $attr = unpack($unpack_header, $buffer); - $name = trim(@$attr['fname']); - $mode = octdec(trim(@$attr['mode'])); - $uid = octdec(trim(@$attr['uid'])); - $gid = octdec(trim(@$attr['gid'])); - $size = octdec(trim(@$attr['size'])); - $mtime = octdec(trim(@$attr['mtime'])); - $chksum = octdec(trim(@$attr['chksum'])); - $typflag = trim(@$attr['typflag']); - $lnkname = trim(@$attr['lnkname']); - $magic = trim(@$attr['magic']); - $ver = trim(@$attr['ver']); - $uname = trim(@$attr['uname']); - $gname = trim(@$attr['gname']); - $devmaj = octdec(trim(@$attr['devmaj'])); - $devmin = octdec(trim(@$attr['devmin'])); - $prefix = trim(@$attr['prefix']); + $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; @@ -79,18 +82,18 @@ class getid3_tar { } // Read to the next chunk - fseek($fd, $size, SEEK_CUR); + fseek($this->getid3->fp, $size, SEEK_CUR); $diff = $size % 512; if ($diff != 0) { // Padding, throw away - fseek($fd, (512 - $diff), SEEK_CUR); + fseek($this->getid3->fp, (512 - $diff), SEEK_CUR); } // Protect against tar-files with garbage at the end if ($name == '') { break; } - $ThisFileInfo['tar']['file_details'][$name] = array ( + $info['tar']['file_details'][$name] = array ( 'name' => $name, 'mode_raw' => $mode, 'mode' => getid3_tar::display_perms($mode), @@ -108,7 +111,7 @@ class getid3_tar { 'devmajor' => $devmaj, 'devminor' => $devmin ); - $ThisFileInfo['tar']['files'] = getid3_lib::array_merge_clobber($ThisFileInfo['tar']['files'], getid3_lib::CreateDeepArray($ThisFileInfo['tar']['file_details'][$name]['name'], '/', $size)); + $info['tar']['files'] = getid3_lib::array_merge_clobber($info['tar']['files'], getid3_lib::CreateDeepArray($info['tar']['file_details'][$name]['name'], '/', $size)); } return true; } @@ -167,7 +170,7 @@ class getid3_tar { 'S' => 'LF_SPARSE', 'V' => 'LF_VOLHDR' ); - return @$flag_types[$typflag]; + return (isset($flag_types[$typflag]) ? $flag_types[$typflag] : ''); } } diff --git a/apps/media/getID3/getid3/module.archive.zip.php b/3rdparty/getid3/module.archive.zip.php similarity index 66% rename from apps/media/getID3/getid3/module.archive.zip.php rename to 3rdparty/getid3/module.archive.zip.php index 1d2be0691f..7db8fd23f1 100644 --- a/apps/media/getID3/getid3/module.archive.zip.php +++ b/3rdparty/getid3/module.archive.zip.php @@ -14,63 +14,67 @@ ///////////////////////////////////////////////////////////////// -class getid3_zip +class getid3_zip extends getid3_handler { - function getid3_zip(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - $ThisFileInfo['fileformat'] = 'zip'; - $ThisFileInfo['zip']['encoding'] = 'ISO-8859-1'; - $ThisFileInfo['zip']['files'] = array(); + $info['fileformat'] = 'zip'; + $info['zip']['encoding'] = 'ISO-8859-1'; + $info['zip']['files'] = array(); - $ThisFileInfo['zip']['compressed_size'] = 0; - $ThisFileInfo['zip']['uncompressed_size'] = 0; - $ThisFileInfo['zip']['entries_count'] = 0; + $info['zip']['compressed_size'] = 0; + $info['zip']['uncompressed_size'] = 0; + $info['zip']['entries_count'] = 0; - if ($ThisFileInfo['filesize'] < pow(2, 31)) { + 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($fd, -128 * $EOCDsearchCounter, SEEK_END); - $EOCDsearchData = fread($fd, 128).$EOCDsearchData; + 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($fd, (-128 * $EOCDsearchCounter) + $EOCDposition, SEEK_END); - $ThisFileInfo['zip']['end_central_directory'] = $this->ZIPparseEndOfCentralDirectory($fd); + fseek($this->getid3->fp, (-128 * $EOCDsearchCounter) + $EOCDposition, SEEK_END); + $info['zip']['end_central_directory'] = $this->ZIPparseEndOfCentralDirectory(); - fseek($fd, $ThisFileInfo['zip']['end_central_directory']['directory_offset'], SEEK_SET); - $ThisFileInfo['zip']['entries_count'] = 0; - while ($centraldirectoryentry = $this->ZIPparseCentralDirectory($fd)) { - $ThisFileInfo['zip']['central_directory'][] = $centraldirectoryentry; - $ThisFileInfo['zip']['entries_count']++; - $ThisFileInfo['zip']['compressed_size'] += $centraldirectoryentry['compressed_size']; - $ThisFileInfo['zip']['uncompressed_size'] += $centraldirectoryentry['uncompressed_size']; + 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) { - $ThisFileInfo['zip']['files'] = getid3_lib::array_merge_clobber($ThisFileInfo['zip']['files'], getid3_lib::CreateDeepArray($centraldirectoryentry['filename'], '/', $centraldirectoryentry['uncompressed_size'])); + $info['zip']['files'] = getid3_lib::array_merge_clobber($info['zip']['files'], getid3_lib::CreateDeepArray($centraldirectoryentry['filename'], '/', $centraldirectoryentry['uncompressed_size'])); } } - if ($ThisFileInfo['zip']['entries_count'] == 0) { - $ThisFileInfo['error'][] = 'No Central Directory entries found (truncated file?)'; + if ($info['zip']['entries_count'] == 0) { + $info['error'][] = 'No Central Directory entries found (truncated file?)'; return false; } - if (!empty($ThisFileInfo['zip']['end_central_directory']['comment'])) { - $ThisFileInfo['zip']['comments']['comment'][] = $ThisFileInfo['zip']['end_central_directory']['comment']; + if (!empty($info['zip']['end_central_directory']['comment'])) { + $info['zip']['comments']['comment'][] = $info['zip']['end_central_directory']['comment']; } - if (isset($ThisFileInfo['zip']['central_directory'][0]['compression_method'])) { - $ThisFileInfo['zip']['compression_method'] = $ThisFileInfo['zip']['central_directory'][0]['compression_method']; + if (isset($info['zip']['central_directory'][0]['compression_method'])) { + $info['zip']['compression_method'] = $info['zip']['central_directory'][0]['compression_method']; } - if (isset($ThisFileInfo['zip']['central_directory'][0]['flags']['compression_speed'])) { - $ThisFileInfo['zip']['compression_speed'] = $ThisFileInfo['zip']['central_directory'][0]['flags']['compression_speed']; + if (isset($info['zip']['central_directory'][0]['flags']['compression_speed'])) { + $info['zip']['compression_speed'] = $info['zip']['central_directory'][0]['flags']['compression_speed']; } - if (isset($ThisFileInfo['zip']['compression_method']) && ($ThisFileInfo['zip']['compression_method'] == 'store') && !isset($ThisFileInfo['zip']['compression_speed'])) { - $ThisFileInfo['zip']['compression_speed'] = 'store'; + if (isset($info['zip']['compression_method']) && ($info['zip']['compression_method'] == 'store') && !isset($info['zip']['compression_speed'])) { + $info['zip']['compression_speed'] = 'store'; } return true; @@ -79,88 +83,92 @@ class getid3_zip } } - if ($this->getZIPentriesFilepointer($fd, $ThisFileInfo)) { + 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 ($ThisFileInfo['zip']['compressed_size'] > ($ThisFileInfo['filesize'] - 46 - 22)) { - $ThisFileInfo['error'][] = 'Warning: Truncated file! - Total compressed file sizes ('.$ThisFileInfo['zip']['compressed_size'].' bytes) is greater than filesize minus Central Directory and End Of Central Directory structures ('.($ThisFileInfo['filesize'] - 46 - 22).' bytes)'; + 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)'; } - $ThisFileInfo['error'][] = 'Cannot find End Of Central Directory - returned list of files in [zip][entries] array may not be complete'; - foreach ($ThisFileInfo['zip']['entries'] as $key => $valuearray) { - $ThisFileInfo['zip']['files'][$valuearray['filename']] = $valuearray['uncompressed_size']; + $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($ThisFileInfo['zip']); - $ThisFileInfo['fileformat'] = ''; - $ThisFileInfo['error'][] = 'Cannot find End Of Central Directory (truncated file?)'; + unset($info['zip']); + $info['fileformat'] = ''; + $info['error'][] = 'Cannot find End Of Central Directory (truncated file?)'; return false; } } - function getZIPHeaderFilepointerTopDown(&$fd, &$ThisFileInfo) { - $ThisFileInfo['fileformat'] = 'zip'; + function getZIPHeaderFilepointerTopDown() { + $info = &$this->getid3->info; - $ThisFileInfo['zip']['compressed_size'] = 0; - $ThisFileInfo['zip']['uncompressed_size'] = 0; - $ThisFileInfo['zip']['entries_count'] = 0; + $info['fileformat'] = 'zip'; - rewind($fd); - while ($fileentry = $this->ZIPparseLocalFileHeader($fd)) { - $ThisFileInfo['zip']['entries'][] = $fileentry; - $ThisFileInfo['zip']['entries_count']++; + $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 ($ThisFileInfo['zip']['entries_count'] == 0) { - $ThisFileInfo['error'][] = 'No Local File Header entries found'; + if ($info['zip']['entries_count'] == 0) { + $info['error'][] = 'No Local File Header entries found'; return false; } - $ThisFileInfo['zip']['entries_count'] = 0; - while ($centraldirectoryentry = $this->ZIPparseCentralDirectory($fd)) { - $ThisFileInfo['zip']['central_directory'][] = $centraldirectoryentry; - $ThisFileInfo['zip']['entries_count']++; - $ThisFileInfo['zip']['compressed_size'] += $centraldirectoryentry['compressed_size']; - $ThisFileInfo['zip']['uncompressed_size'] += $centraldirectoryentry['uncompressed_size']; + $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 ($ThisFileInfo['zip']['entries_count'] == 0) { - $ThisFileInfo['error'][] = 'No Central Directory entries found (truncated file?)'; + if ($info['zip']['entries_count'] == 0) { + $info['error'][] = 'No Central Directory entries found (truncated file?)'; return false; } - if ($EOCD = $this->ZIPparseEndOfCentralDirectory($fd)) { - $ThisFileInfo['zip']['end_central_directory'] = $EOCD; + if ($EOCD = $this->ZIPparseEndOfCentralDirectory()) { + $info['zip']['end_central_directory'] = $EOCD; } else { - $ThisFileInfo['error'][] = 'No End Of Central Directory entry found (truncated file?)'; + $info['error'][] = 'No End Of Central Directory entry found (truncated file?)'; return false; } - if (!empty($ThisFileInfo['zip']['end_central_directory']['comment'])) { - $ThisFileInfo['zip']['comments']['comment'][] = $ThisFileInfo['zip']['end_central_directory']['comment']; + if (!empty($info['zip']['end_central_directory']['comment'])) { + $info['zip']['comments']['comment'][] = $info['zip']['end_central_directory']['comment']; } return true; } - function getZIPentriesFilepointer(&$fd, &$ThisFileInfo) { - $ThisFileInfo['zip']['compressed_size'] = 0; - $ThisFileInfo['zip']['uncompressed_size'] = 0; - $ThisFileInfo['zip']['entries_count'] = 0; + function getZIPentriesFilepointer() { + $info = &$this->getid3->info; - rewind($fd); - while ($fileentry = $this->ZIPparseLocalFileHeader($fd)) { - $ThisFileInfo['zip']['entries'][] = $fileentry; - $ThisFileInfo['zip']['entries_count']++; - $ThisFileInfo['zip']['compressed_size'] += $fileentry['compressed_size']; - $ThisFileInfo['zip']['uncompressed_size'] += $fileentry['uncompressed_size']; + $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 ($ThisFileInfo['zip']['entries_count'] == 0) { - $ThisFileInfo['error'][] = 'No Local File Header entries found'; + if ($info['zip']['entries_count'] == 0) { + $info['error'][] = 'No Local File Header entries found'; return false; } @@ -168,15 +176,15 @@ class getid3_zip } - function ZIPparseLocalFileHeader(&$fd) { - $LocalFileHeader['offset'] = ftell($fd); + function ZIPparseLocalFileHeader() { + $LocalFileHeader['offset'] = ftell($this->getid3->fp); - $ZIPlocalFileHeader = fread($fd, 30); + $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($fd, $LocalFileHeader['offset'], SEEK_SET); // seek back to where filepointer originally was so it can be handled properly + 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)); @@ -200,7 +208,7 @@ class getid3_zip $FilenameExtrafieldLength = $LocalFileHeader['raw']['filename_length'] + $LocalFileHeader['raw']['extra_field_length']; if ($FilenameExtrafieldLength > 0) { - $ZIPlocalFileHeader .= fread($fd, $FilenameExtrafieldLength); + $ZIPlocalFileHeader .= fread($this->getid3->fp, $FilenameExtrafieldLength); if ($LocalFileHeader['raw']['filename_length'] > 0) { $LocalFileHeader['filename'] = substr($ZIPlocalFileHeader, 30, $LocalFileHeader['raw']['filename_length']); @@ -210,12 +218,12 @@ class getid3_zip } } - $LocalFileHeader['data_offset'] = ftell($fd); - //$LocalFileHeader['compressed_data'] = fread($fd, $LocalFileHeader['raw']['compressed_size']); - fseek($fd, $LocalFileHeader['raw']['compressed_size'], SEEK_CUR); + $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($fd, 12); + $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)); @@ -225,15 +233,15 @@ class getid3_zip } - function ZIPparseCentralDirectory(&$fd) { - $CentralDirectory['offset'] = ftell($fd); + function ZIPparseCentralDirectory() { + $CentralDirectory['offset'] = ftell($this->getid3->fp); - $ZIPcentralDirectory = fread($fd, 46); + $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($fd, $CentralDirectory['offset'], SEEK_SET); // seek back to where filepointer originally was so it can be handled properly + 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)); @@ -265,7 +273,7 @@ class getid3_zip $FilenameExtrafieldCommentLength = $CentralDirectory['raw']['filename_length'] + $CentralDirectory['raw']['extra_field_length'] + $CentralDirectory['raw']['file_comment_length']; if ($FilenameExtrafieldCommentLength > 0) { - $FilenameExtrafieldComment = fread($fd, $FilenameExtrafieldCommentLength); + $FilenameExtrafieldComment = fread($this->getid3->fp, $FilenameExtrafieldCommentLength); if ($CentralDirectory['raw']['filename_length'] > 0) { $CentralDirectory['filename'] = substr($FilenameExtrafieldComment, 0, $CentralDirectory['raw']['filename_length']); @@ -281,15 +289,15 @@ class getid3_zip return $CentralDirectory; } - function ZIPparseEndOfCentralDirectory(&$fd) { - $EndOfCentralDirectory['offset'] = ftell($fd); + function ZIPparseEndOfCentralDirectory() { + $EndOfCentralDirectory['offset'] = ftell($this->getid3->fp); - $ZIPendOfCentralDirectory = fread($fd, 22); + $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($fd, $EndOfCentralDirectory['offset'], SEEK_SET); // seek back to where filepointer originally was so it can be handled properly + 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)); @@ -301,14 +309,14 @@ class getid3_zip $EndOfCentralDirectory['comment_length'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 20, 2)); if ($EndOfCentralDirectory['comment_length'] > 0) { - $EndOfCentralDirectory['comment'] = fread($fd, $EndOfCentralDirectory['comment_length']); + $EndOfCentralDirectory['comment'] = fread($this->getid3->fp, $EndOfCentralDirectory['comment_length']); } return $EndOfCentralDirectory; } - function ZIPparseGeneralPurposeFlags($flagbytes, $compressionmethod) { + static function ZIPparseGeneralPurposeFlags($flagbytes, $compressionmethod) { $ParsedFlags['encrypted'] = (bool) ($flagbytes & 0x0001); switch ($compressionmethod) { @@ -341,7 +349,7 @@ class getid3_zip } - function ZIPversionOSLookup($index) { + static function ZIPversionOSLookup($index) { static $ZIPversionOSLookup = array( 0 => 'MS-DOS and OS/2 (FAT / VFAT / FAT32 file systems)', 1 => 'Amiga', @@ -366,7 +374,7 @@ class getid3_zip return (isset($ZIPversionOSLookup[$index]) ? $ZIPversionOSLookup[$index] : '[unknown]'); } - function ZIPcompressionMethodLookup($index) { + static function ZIPcompressionMethodLookup($index) { static $ZIPcompressionMethodLookup = array( 0 => 'store', 1 => 'shrink', @@ -384,7 +392,7 @@ class getid3_zip return (isset($ZIPcompressionMethodLookup[$index]) ? $ZIPcompressionMethodLookup[$index] : '[unknown]'); } - function DOStime2UNIXtime($DOSdate, $DOStime) { + 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 diff --git a/apps/media/getID3/getid3/module.audio-video.asf.php b/3rdparty/getid3/module.audio-video.asf.php similarity index 69% rename from apps/media/getID3/getid3/module.audio-video.asf.php rename to 3rdparty/getid3/module.audio-video.asf.php index 4526291be2..0bb095f52e 100644 --- a/apps/media/getID3/getid3/module.audio-video.asf.php +++ b/3rdparty/getid3/module.audio-video.asf.php @@ -15,24 +15,29 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); -$GUIDarray = getid3_asf::KnownGUIDs(); -foreach ($GUIDarray as $GUIDname => $hexstringvalue) { - // initialize all GUID constants - define($GUIDname, getid3_asf::GUIDtoBytestring($hexstringvalue)); -} - - - -class getid3_asf +class getid3_asf extends getid3_handler { - function getid3_asf(&$fd, &$ThisFileInfo) { + function __construct(getID3 $getid3) { + parent::__construct($getid3); // extends getid3_handler::__construct() + + // initialize all GUID constants + $GUIDarray = $this->KnownGUIDs(); + foreach ($GUIDarray as $GUIDname => $hexstringvalue) { + if (!defined($GUIDname)) { + define($GUIDname, $this->GUIDtoBytestring($hexstringvalue)); + } + } + } + + function Analyze() { + $info = &$this->getid3->info; // Shortcuts - $thisfile_audio = &$ThisFileInfo['audio']; - $thisfile_video = &$ThisFileInfo['video']; - $ThisFileInfo['asf'] = array(); - $thisfile_asf = &$ThisFileInfo['asf']; + $thisfile_audio = &$info['audio']; + $thisfile_video = &$info['video']; + $info['asf'] = array(); + $thisfile_asf = &$info['asf']; $thisfile_asf['comments'] = array(); $thisfile_asf_comments = &$thisfile_asf['comments']; $thisfile_asf['header_object'] = array(); @@ -59,17 +64,17 @@ class getid3_asf // Reserved1 BYTE 8 // hardcoded: 0x01 // Reserved2 BYTE 8 // hardcoded: 0x02 - $ThisFileInfo['fileformat'] = 'asf'; + $info['fileformat'] = 'asf'; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $HeaderObjectData = fread($fd, 30); + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $HeaderObjectData = fread($this->getid3->fp, 30); $thisfile_asf_headerobject['objectid'] = substr($HeaderObjectData, 0, 16); $thisfile_asf_headerobject['objectid_guid'] = $this->BytestringToGUID($thisfile_asf_headerobject['objectid']); if ($thisfile_asf_headerobject['objectid'] != GETID3_ASF_Header_Object) { - $ThisFileInfo['warning'][] = 'ASF header GUID {'.$this->BytestringToGUID($thisfile_asf_headerobject['objectid']).'} does not match expected "GETID3_ASF_Header_Object" GUID {'.$this->BytestringToGUID(GETID3_ASF_Header_Object).'}'; - unset($ThisFileInfo['fileformat']); - unset($ThisFileInfo['asf']); + $info['warning'][] = 'ASF header GUID {'.$this->BytestringToGUID($thisfile_asf_headerobject['objectid']).'} does not match expected "GETID3_ASF_Header_Object" GUID {'.$this->BytestringToGUID(GETID3_ASF_Header_Object).'}'; + unset($info['fileformat']); + unset($info['asf']); return false; break; } @@ -78,11 +83,12 @@ class getid3_asf $thisfile_asf_headerobject['reserved1'] = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 28, 1)); $thisfile_asf_headerobject['reserved2'] = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 29, 1)); - $ASFHeaderData = fread($fd, $thisfile_asf_headerobject['objectsize'] - 30); + $NextObjectOffset = ftell($this->getid3->fp); + $ASFHeaderData = fread($this->getid3->fp, $thisfile_asf_headerobject['objectsize'] - 30); $offset = 0; for ($HeaderObjectsCounter = 0; $HeaderObjectsCounter < $thisfile_asf_headerobject['headerobjects']; $HeaderObjectsCounter++) { - $NextObjectGUID = substr($ASFHeaderData, $offset, 16); + $NextObjectGUID = substr($ASFHeaderData, $offset, 16); $offset += 16; $NextObjectGUIDtext = $this->BytestringToGUID($NextObjectGUID); $NextObjectSize = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); @@ -113,6 +119,7 @@ class getid3_asf $thisfile_asf['file_properties_object'] = array(); $thisfile_asf_filepropertiesobject = &$thisfile_asf['file_properties_object']; + $thisfile_asf_filepropertiesobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_filepropertiesobject['objectid'] = $NextObjectGUID; $thisfile_asf_filepropertiesobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_filepropertiesobject['objectsize'] = $NextObjectSize; @@ -157,10 +164,10 @@ class getid3_asf } else { // broadcast flag NOT set, perform calculations - $ThisFileInfo['playtime_seconds'] = ($thisfile_asf_filepropertiesobject['play_duration'] / 10000000) - ($thisfile_asf_filepropertiesobject['preroll'] / 1000); + $info['playtime_seconds'] = ($thisfile_asf_filepropertiesobject['play_duration'] / 10000000) - ($thisfile_asf_filepropertiesobject['preroll'] / 1000); - //$ThisFileInfo['bitrate'] = $thisfile_asf_filepropertiesobject['max_bitrate']; - $ThisFileInfo['bitrate'] = ((isset($thisfile_asf_filepropertiesobject['filesize']) ? $thisfile_asf_filepropertiesobject['filesize'] : $ThisFileInfo['filesize']) * 8) / $ThisFileInfo['playtime_seconds']; + //$info['bitrate'] = $thisfile_asf_filepropertiesobject['max_bitrate']; + $info['bitrate'] = ((isset($thisfile_asf_filepropertiesobject['filesize']) ? $thisfile_asf_filepropertiesobject['filesize'] : $info['filesize']) * 8) / $info['playtime_seconds']; } break; @@ -186,6 +193,7 @@ class getid3_asf // stream number isn't known until halfway through decoding the structure, hence it // it is decoded to a temporary variable and then stuck in the appropriate index later + $StreamPropertiesObjectData['offset'] = $NextObjectOffset + $offset; $StreamPropertiesObjectData['objectid'] = $NextObjectGUID; $StreamPropertiesObjectData['objectid_guid'] = $NextObjectGUIDtext; $StreamPropertiesObjectData['objectsize'] = $NextObjectSize; @@ -253,6 +261,7 @@ class getid3_asf $thisfile_asf['header_extension_object'] = array(); $thisfile_asf_headerextensionobject = &$thisfile_asf['header_extension_object']; + $thisfile_asf_headerextensionobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_headerextensionobject['objectid'] = $NextObjectGUID; $thisfile_asf_headerextensionobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_headerextensionobject['objectsize'] = $NextObjectSize; @@ -260,20 +269,25 @@ class getid3_asf $offset += 16; $thisfile_asf_headerextensionobject['reserved_1_guid'] = $this->BytestringToGUID($thisfile_asf_headerextensionobject['reserved_1']); if ($thisfile_asf_headerextensionobject['reserved_1'] != GETID3_ASF_Reserved_1) { - $ThisFileInfo['warning'][] = 'header_extension_object.reserved_1 GUID ('.$this->BytestringToGUID($thisfile_asf_headerextensionobject['reserved_1']).') does not match expected "GETID3_ASF_Reserved_1" GUID ('.$this->BytestringToGUID(GETID3_ASF_Reserved_1).')'; + $info['warning'][] = 'header_extension_object.reserved_1 GUID ('.$this->BytestringToGUID($thisfile_asf_headerextensionobject['reserved_1']).') does not match expected "GETID3_ASF_Reserved_1" GUID ('.$this->BytestringToGUID(GETID3_ASF_Reserved_1).')'; //return false; break; } $thisfile_asf_headerextensionobject['reserved_2'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; if ($thisfile_asf_headerextensionobject['reserved_2'] != 6) { - $ThisFileInfo['warning'][] = 'header_extension_object.reserved_2 ('.getid3_lib::PrintHexBytes($thisfile_asf_headerextensionobject['reserved_2']).') does not match expected value of "6"'; + $info['warning'][] = 'header_extension_object.reserved_2 ('.getid3_lib::PrintHexBytes($thisfile_asf_headerextensionobject['reserved_2']).') does not match expected value of "6"'; //return false; break; } $thisfile_asf_headerextensionobject['extension_data_size'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); $offset += 4; $thisfile_asf_headerextensionobject['extension_data'] = substr($ASFHeaderData, $offset, $thisfile_asf_headerextensionobject['extension_data_size']); + $unhandled_sections = 0; + $thisfile_asf_headerextensionobject['extension_data_parsed'] = $this->ASF_HeaderExtensionObjectDataParse($thisfile_asf_headerextensionobject['extension_data'], $unhandled_sections); + if ($unhandled_sections === 0) { + unset($thisfile_asf_headerextensionobject['extension_data']); + } $offset += $thisfile_asf_headerextensionobject['extension_data_size']; break; @@ -297,6 +311,7 @@ class getid3_asf $thisfile_asf['codec_list_object'] = array(); $thisfile_asf_codeclistobject = &$thisfile_asf['codec_list_object']; + $thisfile_asf_codeclistobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_codeclistobject['objectid'] = $NextObjectGUID; $thisfile_asf_codeclistobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_codeclistobject['objectsize'] = $NextObjectSize; @@ -304,7 +319,7 @@ class getid3_asf $offset += 16; $thisfile_asf_codeclistobject['reserved_guid'] = $this->BytestringToGUID($thisfile_asf_codeclistobject['reserved']); if ($thisfile_asf_codeclistobject['reserved'] != $this->GUIDtoBytestring('86D15241-311D-11D0-A3A4-00A0C90348F6')) { - $ThisFileInfo['warning'][] = 'codec_list_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_codeclistobject['reserved']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {86D15241-311D-11D0-A3A4-00A0C90348F6}'; + $info['warning'][] = 'codec_list_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_codeclistobject['reserved']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {86D15241-311D-11D0-A3A4-00A0C90348F6}'; //return false; break; } @@ -334,82 +349,84 @@ class getid3_asf $thisfile_asf_codeclistobject_codecentries_current['information'] = substr($ASFHeaderData, $offset, $CodecInformationLength); $offset += $CodecInformationLength; - if ($thisfile_asf_codeclistobject_codecentries_current['type_raw'] == 2) { - // audio codec + if ($thisfile_asf_codeclistobject_codecentries_current['type_raw'] == 2) { // audio codec + if (strpos($thisfile_asf_codeclistobject_codecentries_current['description'], ',') === false) { - $ThisFileInfo['error'][] = '[asf][codec_list_object][codec_entries]['.$CodecEntryCounter.'][description] expected to contain comma-seperated list of parameters: "'.$thisfile_asf_codeclistobject_codecentries_current['description'].'"'; - return false; - } - list($AudioCodecBitrate, $AudioCodecFrequency, $AudioCodecChannels) = explode(',', $this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['description'])); - $thisfile_audio['codec'] = $this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['name']); + $info['warning'][] = '[asf][codec_list_object][codec_entries]['.$CodecEntryCounter.'][description] expected to contain comma-seperated list of parameters: "'.$thisfile_asf_codeclistobject_codecentries_current['description'].'"'; + } else { - if (!isset($thisfile_audio['bitrate']) && strstr($AudioCodecBitrate, 'kbps')) { - $thisfile_audio['bitrate'] = (int) (trim(str_replace('kbps', '', $AudioCodecBitrate)) * 1000); - } - //if (!isset($thisfile_video['bitrate']) && isset($thisfile_audio['bitrate']) && isset($thisfile_asf['file_properties_object']['max_bitrate']) && ($thisfile_asf_codeclistobject['codec_entries_count'] > 1)) { - if (!@$thisfile_video['bitrate'] && @$thisfile_audio['bitrate'] && @$ThisFileInfo['bitrate']) { - //$thisfile_video['bitrate'] = $thisfile_asf['file_properties_object']['max_bitrate'] - $thisfile_audio['bitrate']; - $thisfile_video['bitrate'] = $ThisFileInfo['bitrate'] - $thisfile_audio['bitrate']; - } + list($AudioCodecBitrate, $AudioCodecFrequency, $AudioCodecChannels) = explode(',', $this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['description'])); + $thisfile_audio['codec'] = $this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['name']); - $AudioCodecFrequency = (int) trim(str_replace('kHz', '', $AudioCodecFrequency)); - switch ($AudioCodecFrequency) { - case 8: - case 8000: - $thisfile_audio['sample_rate'] = 8000; - break; - - case 11: - case 11025: - $thisfile_audio['sample_rate'] = 11025; - break; - - case 12: - case 12000: - $thisfile_audio['sample_rate'] = 12000; - break; - - case 16: - case 16000: - $thisfile_audio['sample_rate'] = 16000; - break; - - case 22: - case 22050: - $thisfile_audio['sample_rate'] = 22050; - break; - - case 24: - case 24000: - $thisfile_audio['sample_rate'] = 24000; - break; - - case 32: - case 32000: - $thisfile_audio['sample_rate'] = 32000; - break; - - case 44: - case 441000: - $thisfile_audio['sample_rate'] = 44100; - break; - - case 48: - case 48000: - $thisfile_audio['sample_rate'] = 48000; - break; - - default: - $ThisFileInfo['warning'][] = 'unknown frequency: "'.$AudioCodecFrequency.'" ('.$this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['description']).')'; - break; - } - - if (!isset($thisfile_audio['channels'])) { - if (strstr($AudioCodecChannels, 'stereo')) { - $thisfile_audio['channels'] = 2; - } elseif (strstr($AudioCodecChannels, 'mono')) { - $thisfile_audio['channels'] = 1; + if (!isset($thisfile_audio['bitrate']) && strstr($AudioCodecBitrate, 'kbps')) { + $thisfile_audio['bitrate'] = (int) (trim(str_replace('kbps', '', $AudioCodecBitrate)) * 1000); } + //if (!isset($thisfile_video['bitrate']) && isset($thisfile_audio['bitrate']) && isset($thisfile_asf['file_properties_object']['max_bitrate']) && ($thisfile_asf_codeclistobject['codec_entries_count'] > 1)) { + if (empty($thisfile_video['bitrate']) && !empty($thisfile_audio['bitrate']) && !empty($info['bitrate'])) { + //$thisfile_video['bitrate'] = $thisfile_asf['file_properties_object']['max_bitrate'] - $thisfile_audio['bitrate']; + $thisfile_video['bitrate'] = $info['bitrate'] - $thisfile_audio['bitrate']; + } + + $AudioCodecFrequency = (int) trim(str_replace('kHz', '', $AudioCodecFrequency)); + switch ($AudioCodecFrequency) { + case 8: + case 8000: + $thisfile_audio['sample_rate'] = 8000; + break; + + case 11: + case 11025: + $thisfile_audio['sample_rate'] = 11025; + break; + + case 12: + case 12000: + $thisfile_audio['sample_rate'] = 12000; + break; + + case 16: + case 16000: + $thisfile_audio['sample_rate'] = 16000; + break; + + case 22: + case 22050: + $thisfile_audio['sample_rate'] = 22050; + break; + + case 24: + case 24000: + $thisfile_audio['sample_rate'] = 24000; + break; + + case 32: + case 32000: + $thisfile_audio['sample_rate'] = 32000; + break; + + case 44: + case 441000: + $thisfile_audio['sample_rate'] = 44100; + break; + + case 48: + case 48000: + $thisfile_audio['sample_rate'] = 48000; + break; + + default: + $info['warning'][] = 'unknown frequency: "'.$AudioCodecFrequency.'" ('.$this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['description']).')'; + break; + } + + if (!isset($thisfile_audio['channels'])) { + if (strstr($AudioCodecChannels, 'stereo')) { + $thisfile_audio['channels'] = 2; + } elseif (strstr($AudioCodecChannels, 'mono')) { + $thisfile_audio['channels'] = 1; + } + } + } } } @@ -436,6 +453,7 @@ class getid3_asf $thisfile_asf['script_command_object'] = array(); $thisfile_asf_scriptcommandobject = &$thisfile_asf['script_command_object']; + $thisfile_asf_scriptcommandobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_scriptcommandobject['objectid'] = $NextObjectGUID; $thisfile_asf_scriptcommandobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_scriptcommandobject['objectsize'] = $NextObjectSize; @@ -443,7 +461,7 @@ class getid3_asf $offset += 16; $thisfile_asf_scriptcommandobject['reserved_guid'] = $this->BytestringToGUID($thisfile_asf_scriptcommandobject['reserved']); if ($thisfile_asf_scriptcommandobject['reserved'] != $this->GUIDtoBytestring('4B1ACBE3-100B-11D0-A39B-00A0C90348F6')) { - $ThisFileInfo['warning'][] = 'script_command_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_scriptcommandobject['reserved']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {4B1ACBE3-100B-11D0-A39B-00A0C90348F6}'; + $info['warning'][] = 'script_command_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_scriptcommandobject['reserved']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {4B1ACBE3-100B-11D0-A39B-00A0C90348F6}'; //return false; break; } @@ -494,6 +512,7 @@ class getid3_asf $thisfile_asf['marker_object'] = array(); $thisfile_asf_markerobject = &$thisfile_asf['marker_object']; + $thisfile_asf_markerobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_markerobject['objectid'] = $NextObjectGUID; $thisfile_asf_markerobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_markerobject['objectsize'] = $NextObjectSize; @@ -501,7 +520,7 @@ class getid3_asf $offset += 16; $thisfile_asf_markerobject['reserved_guid'] = $this->BytestringToGUID($thisfile_asf_markerobject['reserved']); if ($thisfile_asf_markerobject['reserved'] != $this->GUIDtoBytestring('4CFEDB20-75F6-11CF-9C0F-00A0C90349CB')) { - $ThisFileInfo['warning'][] = 'marker_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_markerobject['reserved_1']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {4CFEDB20-75F6-11CF-9C0F-00A0C90349CB}'; + $info['warning'][] = 'marker_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_markerobject['reserved_1']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {4CFEDB20-75F6-11CF-9C0F-00A0C90349CB}'; break; } $thisfile_asf_markerobject['markers_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); @@ -509,7 +528,7 @@ class getid3_asf $thisfile_asf_markerobject['reserved_2'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; if ($thisfile_asf_markerobject['reserved_2'] != 0) { - $ThisFileInfo['warning'][] = 'marker_object.reserved_2 ('.getid3_lib::PrintHexBytes($thisfile_asf_markerobject['reserved_2']).') does not match expected value of "0"'; + $info['warning'][] = 'marker_object.reserved_2 ('.getid3_lib::PrintHexBytes($thisfile_asf_markerobject['reserved_2']).') does not match expected value of "0"'; break; } $thisfile_asf_markerobject['name_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); @@ -552,6 +571,7 @@ class getid3_asf $thisfile_asf['bitrate_mutual_exclusion_object'] = array(); $thisfile_asf_bitratemutualexclusionobject = &$thisfile_asf['bitrate_mutual_exclusion_object']; + $thisfile_asf_bitratemutualexclusionobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_bitratemutualexclusionobject['objectid'] = $NextObjectGUID; $thisfile_asf_bitratemutualexclusionobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_bitratemutualexclusionobject['objectsize'] = $NextObjectSize; @@ -559,7 +579,7 @@ class getid3_asf $thisfile_asf_bitratemutualexclusionobject['reserved_guid'] = $this->BytestringToGUID($thisfile_asf_bitratemutualexclusionobject['reserved']); $offset += 16; if (($thisfile_asf_bitratemutualexclusionobject['reserved'] != GETID3_ASF_Mutex_Bitrate) && ($thisfile_asf_bitratemutualexclusionobject['reserved'] != GETID3_ASF_Mutex_Unknown)) { - $ThisFileInfo['warning'][] = 'bitrate_mutual_exclusion_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_bitratemutualexclusionobject['reserved']).'} does not match expected "GETID3_ASF_Mutex_Bitrate" GUID {'.$this->BytestringToGUID(GETID3_ASF_Mutex_Bitrate).'} or "GETID3_ASF_Mutex_Unknown" GUID {'.$this->BytestringToGUID(GETID3_ASF_Mutex_Unknown).'}'; + $info['warning'][] = 'bitrate_mutual_exclusion_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_bitratemutualexclusionobject['reserved']).'} does not match expected "GETID3_ASF_Mutex_Bitrate" GUID {'.$this->BytestringToGUID(GETID3_ASF_Mutex_Bitrate).'} or "GETID3_ASF_Mutex_Unknown" GUID {'.$this->BytestringToGUID(GETID3_ASF_Mutex_Unknown).'}'; //return false; break; } @@ -584,6 +604,7 @@ class getid3_asf $thisfile_asf['error_correction_object'] = array(); $thisfile_asf_errorcorrectionobject = &$thisfile_asf['error_correction_object']; + $thisfile_asf_errorcorrectionobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_errorcorrectionobject['objectid'] = $NextObjectGUID; $thisfile_asf_errorcorrectionobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_errorcorrectionobject['objectsize'] = $NextObjectSize; @@ -619,7 +640,7 @@ class getid3_asf break; default: - $ThisFileInfo['warning'][] = 'error_correction_object.error_correction_type GUID {'.$this->BytestringToGUID($thisfile_asf_errorcorrectionobject['reserved']).'} does not match expected "GETID3_ASF_No_Error_Correction" GUID {'.$this->BytestringToGUID(GETID3_ASF_No_Error_Correction).'} or "GETID3_ASF_Audio_Spread" GUID {'.$this->BytestringToGUID(GETID3_ASF_Audio_Spread).'}'; + $info['warning'][] = 'error_correction_object.error_correction_type GUID {'.$this->BytestringToGUID($thisfile_asf_errorcorrectionobject['reserved']).'} does not match expected "GETID3_ASF_No_Error_Correction" GUID {'.$this->BytestringToGUID(GETID3_ASF_No_Error_Correction).'} or "GETID3_ASF_Audio_Spread" GUID {'.$this->BytestringToGUID(GETID3_ASF_Audio_Spread).'}'; //return false; break; } @@ -646,6 +667,7 @@ class getid3_asf $thisfile_asf['content_description_object'] = array(); $thisfile_asf_contentdescriptionobject = &$thisfile_asf['content_description_object']; + $thisfile_asf_contentdescriptionobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_contentdescriptionobject['objectid'] = $NextObjectGUID; $thisfile_asf_contentdescriptionobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_contentdescriptionobject['objectsize'] = $NextObjectSize; @@ -701,6 +723,7 @@ class getid3_asf $thisfile_asf['extended_content_description_object'] = array(); $thisfile_asf_extendedcontentdescriptionobject = &$thisfile_asf['extended_content_description_object']; + $thisfile_asf_extendedcontentdescriptionobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_extendedcontentdescriptionobject['objectid'] = $NextObjectGUID; $thisfile_asf_extendedcontentdescriptionobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_extendedcontentdescriptionobject['objectsize'] = $NextObjectSize; @@ -709,7 +732,7 @@ class getid3_asf for ($ExtendedContentDescriptorsCounter = 0; $ExtendedContentDescriptorsCounter < $thisfile_asf_extendedcontentdescriptionobject['content_descriptors_count']; $ExtendedContentDescriptorsCounter++) { // shortcut $thisfile_asf_extendedcontentdescriptionobject['content_descriptors'][$ExtendedContentDescriptorsCounter] = array(); - $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current = &$thisfile_asf_extendedcontentdescriptionobject['content_descriptors'][$ExtendedContentDescriptorsCounter]; + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current = &$thisfile_asf_extendedcontentdescriptionobject['content_descriptors'][$ExtendedContentDescriptorsCounter]; $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['base_offset'] = $offset + 30; $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); @@ -741,7 +764,7 @@ class getid3_asf break; default: - $ThisFileInfo['warning'][] = 'extended_content_description.content_descriptors.'.$ExtendedContentDescriptorsCounter.'.value_type is invalid ('.$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_type'].')'; + $info['warning'][] = 'extended_content_description.content_descriptors.'.$ExtendedContentDescriptorsCounter.'.value_type is invalid ('.$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_type'].')'; //return false; break; } @@ -749,7 +772,8 @@ class getid3_asf case 'wm/albumartist': case 'artist': - $thisfile_asf_comments['artist'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); + // Note: not 'artist', that comes from 'author' tag + $thisfile_asf_comments['albumartist'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); break; case 'wm/albumtitle': @@ -762,9 +786,19 @@ class getid3_asf $thisfile_asf_comments['genre'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); break; + case 'wm/partofset': + $thisfile_asf_comments['partofset'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); + break; + case 'wm/tracknumber': case 'tracknumber': - $thisfile_asf_comments['track'] = array(intval($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']))); + // be careful casting to int: casting unicode strings to int gives unexpected results (stops parsing at first non-numeric character) + $thisfile_asf_comments['track'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); + foreach ($thisfile_asf_comments['track'] as $key => $value) { + if (preg_match('/^[0-9\x00]+$/', $value)) { + $thisfile_asf_comments['track'][$key] = intval(str_replace("\x00", '', $value)); + } + } break; case 'wm/track': @@ -794,20 +828,20 @@ class getid3_asf case 'id3': // id3v2 module might not be loaded if (class_exists('getid3_id3v2')) { - $tempfile = tempnam('*', 'getID3'); - $tempfilehandle = fopen($tempfile, "wb"); - $tempThisfileInfo = array('encoding'=>$ThisFileInfo['encoding']); + $tempfile = tempnam(GETID3_TEMP_DIR, 'getID3'); + $tempfilehandle = fopen($tempfile, 'wb'); + $tempThisfileInfo = array('encoding'=>$info['encoding']); fwrite($tempfilehandle, $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']); fclose($tempfilehandle); - $tempfilehandle = fopen($tempfile, "rb"); - $id3 = new getid3_id3v2($tempfilehandle, $tempThisfileInfo); - unset($id3); - fclose($tempfilehandle); - unlink($tempfile); + $getid3_temp = new getID3(); + $getid3_temp->openfile($tempfile); + $getid3_id3v2 = new getid3_id3v2($getid3_temp); + $getid3_id3v2->Analyze(); + $info['id3v2'] = $getid3_temp->info['id3v2']; + unset($getid3_temp, $getid3_id3v2); - $ThisFileInfo['id3v2'] = $tempThisfileInfo['id3v2']; - unset($tempThisfileInfo); + unlink($tempfile); } break; @@ -817,14 +851,12 @@ class getid3_asf break; case 'wm/picture': - //typedef struct _WMPicture{ - // LPWSTR pwszMIMEType; - // BYTE bPictureType; - // LPWSTR pwszDescription; - // DWORD dwDataLen; - // BYTE* pbData; - //} WM_PICTURE; - + $WMpicture = $this->ASF_WMpicture($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']); + foreach ($WMpicture as $key => $value) { + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current[$key] = $value; + } + unset($WMpicture); +/* $wm_picture_offset = 0; $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_type_id'] = getid3_lib::LittleEndian2Int(substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset, 1)); $wm_picture_offset += 1; @@ -850,6 +882,18 @@ class getid3_asf $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['data'] = substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset); unset($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']); + $imageinfo = array(); + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime'] = ''; + $imagechunkcheck = getid3_lib::GetDataImageSize($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['data'], $imageinfo); + unset($imageinfo); + if (!empty($imagechunkcheck)) { + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]); + } + if (!isset($thisfile_asf_comments['picture'])) { + $thisfile_asf_comments['picture'] = array(); + } + $thisfile_asf_comments['picture'][] = array('data'=>$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['data'], 'image_mime'=>$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime']); +*/ break; default: @@ -885,6 +929,7 @@ class getid3_asf $thisfile_asf['stream_bitrate_properties_object'] = array(); $thisfile_asf_streambitratepropertiesobject = &$thisfile_asf['stream_bitrate_properties_object']; + $thisfile_asf_streambitratepropertiesobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_streambitratepropertiesobject['objectid'] = $NextObjectGUID; $thisfile_asf_streambitratepropertiesobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_streambitratepropertiesobject['objectsize'] = $NextObjectSize; @@ -910,6 +955,7 @@ class getid3_asf $thisfile_asf['padding_object'] = array(); $thisfile_asf_paddingobject = &$thisfile_asf['padding_object']; + $thisfile_asf_paddingobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_paddingobject['objectid'] = $NextObjectGUID; $thisfile_asf_paddingobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_paddingobject['objectsize'] = $NextObjectSize; @@ -927,9 +973,9 @@ class getid3_asf default: // Implementations shall ignore any standard or non-standard object that they do not know how to handle. if ($this->GUIDname($NextObjectGUIDtext)) { - $ThisFileInfo['warning'][] = 'unhandled GUID "'.$this->GUIDname($NextObjectGUIDtext).'" {'.$NextObjectGUIDtext.'} in ASF header at offset '.($offset - 16 - 8); + $info['warning'][] = 'unhandled GUID "'.$this->GUIDname($NextObjectGUIDtext).'" {'.$NextObjectGUIDtext.'} in ASF header at offset '.($offset - 16 - 8); } else { - $ThisFileInfo['warning'][] = 'unknown GUID {'.$NextObjectGUIDtext.'} in ASF header at offset '.($offset - 16 - 8); + $info['warning'][] = 'unknown GUID {'.$NextObjectGUIDtext.'} in ASF header at offset '.($offset - 16 - 8); } $offset += ($NextObjectSize - 16 - 8); break; @@ -1000,16 +1046,16 @@ class getid3_asf if (!empty($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'])) { foreach ($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'] as $dummy => $dataarray) { - if (@$dataarray['flags']['stream_number'] == $streamnumber) { + if (isset($dataarray['flags']['stream_number']) && ($dataarray['flags']['stream_number'] == $streamnumber)) { $thisfile_asf_audiomedia_currentstream['bitrate'] = $dataarray['bitrate']; $thisfile_audio['bitrate'] += $dataarray['bitrate']; break; } } } else { - if (@$thisfile_asf_audiomedia_currentstream['bytes_sec']) { + if (!empty($thisfile_asf_audiomedia_currentstream['bytes_sec'])) { $thisfile_audio['bitrate'] += $thisfile_asf_audiomedia_currentstream['bytes_sec'] * 8; - } elseif (@$thisfile_asf_audiomedia_currentstream['bitrate']) { + } elseif (!empty($thisfile_asf_audiomedia_currentstream['bitrate'])) { $thisfile_audio['bitrate'] += $thisfile_asf_audiomedia_currentstream['bitrate']; } } @@ -1086,7 +1132,7 @@ class getid3_asf if (!empty($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'])) { foreach ($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'] as $dummy => $dataarray) { - if (@$dataarray['flags']['stream_number'] == $streamnumber) { + if (isset($dataarray['flags']['stream_number']) && ($dataarray['flags']['stream_number'] == $streamnumber)) { $thisfile_asf_videomedia_currentstream['bitrate'] = $dataarray['bitrate']; $thisfile_video['streams'][$streamnumber]['bitrate'] = $dataarray['bitrate']; $thisfile_video['bitrate'] += $dataarray['bitrate']; @@ -1110,8 +1156,8 @@ class getid3_asf } } - while (ftell($fd) < $ThisFileInfo['avdataend']) { - $NextObjectDataHeader = fread($fd, 24); + while (ftell($this->getid3->fp) < $info['avdataend']) { + $NextObjectDataHeader = fread($this->getid3->fp, 24); $offset = 0; $NextObjectGUID = substr($NextObjectDataHeader, 0, 16); $offset += 16; @@ -1133,7 +1179,7 @@ class getid3_asf $thisfile_asf['data_object'] = array(); $thisfile_asf_dataobject = &$thisfile_asf['data_object']; - $DataObjectData = $NextObjectDataHeader.fread($fd, 50 - 24); + $DataObjectData = $NextObjectDataHeader.fread($this->getid3->fp, 50 - 24); $offset = 24; $thisfile_asf_dataobject['objectid'] = $NextObjectGUID; @@ -1148,7 +1194,7 @@ class getid3_asf $thisfile_asf_dataobject['reserved'] = getid3_lib::LittleEndian2Int(substr($DataObjectData, $offset, 2)); $offset += 2; if ($thisfile_asf_dataobject['reserved'] != 0x0101) { - $ThisFileInfo['warning'][] = 'data_object.reserved ('.getid3_lib::PrintHexBytes($thisfile_asf_dataobject['reserved']).') does not match expected value of "0x0101"'; + $info['warning'][] = 'data_object.reserved ('.getid3_lib::PrintHexBytes($thisfile_asf_dataobject['reserved']).') does not match expected value of "0x0101"'; //return false; break; } @@ -1161,9 +1207,9 @@ class getid3_asf // * * Error Correction Present bits 1 // If set, use Opaque Data Packet structure, else use Payload structure // * Error Correction Data - $ThisFileInfo['avdataoffset'] = ftell($fd); - fseek($fd, ($thisfile_asf_dataobject['objectsize'] - 50), SEEK_CUR); // skip actual audio/video data - $ThisFileInfo['avdataend'] = ftell($fd); + $info['avdataoffset'] = ftell($this->getid3->fp); + fseek($this->getid3->fp, ($thisfile_asf_dataobject['objectsize'] - 50), SEEK_CUR); // skip actual audio/video data + $info['avdataend'] = ftell($this->getid3->fp); break; case GETID3_ASF_Simple_Index_Object: @@ -1183,7 +1229,7 @@ class getid3_asf $thisfile_asf['simple_index_object'] = array(); $thisfile_asf_simpleindexobject = &$thisfile_asf['simple_index_object']; - $SimpleIndexObjectData = $NextObjectDataHeader.fread($fd, 56 - 24); + $SimpleIndexObjectData = $NextObjectDataHeader.fread($this->getid3->fp, 56 - 24); $offset = 24; $thisfile_asf_simpleindexobject['objectid'] = $NextObjectGUID; @@ -1200,7 +1246,7 @@ class getid3_asf $thisfile_asf_simpleindexobject['index_entries_count'] = getid3_lib::LittleEndian2Int(substr($SimpleIndexObjectData, $offset, 4)); $offset += 4; - $IndexEntriesData = $SimpleIndexObjectData.fread($fd, 6 * $thisfile_asf_simpleindexobject['index_entries_count']); + $IndexEntriesData = $SimpleIndexObjectData.fread($this->getid3->fp, 6 * $thisfile_asf_simpleindexobject['index_entries_count']); for ($IndexEntriesCounter = 0; $IndexEntriesCounter < $thisfile_asf_simpleindexobject['index_entries_count']; $IndexEntriesCounter++) { $thisfile_asf_simpleindexobject['index_entries'][$IndexEntriesCounter]['packet_number'] = getid3_lib::LittleEndian2Int(substr($IndexEntriesData, $offset, 4)); $offset += 4; @@ -1237,7 +1283,7 @@ class getid3_asf $thisfile_asf['asf_index_object'] = array(); $thisfile_asf_asfindexobject = &$thisfile_asf['asf_index_object']; - $ASFIndexObjectData = $NextObjectDataHeader.fread($fd, 34 - 24); + $ASFIndexObjectData = $NextObjectDataHeader.fread($this->getid3->fp, 34 - 24); $offset = 24; $thisfile_asf_asfindexobject['objectid'] = $NextObjectGUID; @@ -1251,7 +1297,7 @@ class getid3_asf $thisfile_asf_asfindexobject['index_blocks_count'] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4)); $offset += 4; - $ASFIndexObjectData .= fread($fd, 4 * $thisfile_asf_asfindexobject['index_specifiers_count']); + $ASFIndexObjectData .= fread($this->getid3->fp, 4 * $thisfile_asf_asfindexobject['index_specifiers_count']); for ($IndexSpecifiersCounter = 0; $IndexSpecifiersCounter < $thisfile_asf_asfindexobject['index_specifiers_count']; $IndexSpecifiersCounter++) { $IndexSpecifierStreamNumber = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 2)); $offset += 2; @@ -1261,17 +1307,17 @@ class getid3_asf $thisfile_asf_asfindexobject['index_specifiers'][$IndexSpecifiersCounter]['index_type_text'] = $this->ASFIndexObjectIndexTypeLookup($thisfile_asf_asfindexobject['index_specifiers'][$IndexSpecifiersCounter]['index_type']); } - $ASFIndexObjectData .= fread($fd, 4); + $ASFIndexObjectData .= fread($this->getid3->fp, 4); $thisfile_asf_asfindexobject['index_entry_count'] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4)); $offset += 4; - $ASFIndexObjectData .= fread($fd, 8 * $thisfile_asf_asfindexobject['index_specifiers_count']); + $ASFIndexObjectData .= fread($this->getid3->fp, 8 * $thisfile_asf_asfindexobject['index_specifiers_count']); for ($IndexSpecifiersCounter = 0; $IndexSpecifiersCounter < $thisfile_asf_asfindexobject['index_specifiers_count']; $IndexSpecifiersCounter++) { $thisfile_asf_asfindexobject['block_positions'][$IndexSpecifiersCounter] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 8)); $offset += 8; } - $ASFIndexObjectData .= fread($fd, 4 * $thisfile_asf_asfindexobject['index_specifiers_count'] * $thisfile_asf_asfindexobject['index_entry_count']); + $ASFIndexObjectData .= fread($this->getid3->fp, 4 * $thisfile_asf_asfindexobject['index_specifiers_count'] * $thisfile_asf_asfindexobject['index_entry_count']); for ($IndexEntryCounter = 0; $IndexEntryCounter < $thisfile_asf_asfindexobject['index_entry_count']; $IndexEntryCounter++) { for ($IndexSpecifiersCounter = 0; $IndexSpecifiersCounter < $thisfile_asf_asfindexobject['index_specifiers_count']; $IndexSpecifiersCounter++) { $thisfile_asf_asfindexobject['offsets'][$IndexSpecifiersCounter][$IndexEntryCounter] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4)); @@ -1284,11 +1330,11 @@ class getid3_asf default: // Implementations shall ignore any standard or non-standard object that they do not know how to handle. if ($this->GUIDname($NextObjectGUIDtext)) { - $ThisFileInfo['warning'][] = 'unhandled GUID "'.$this->GUIDname($NextObjectGUIDtext).'" {'.$NextObjectGUIDtext.'} in ASF body at offset '.($offset - 16 - 8); + $info['warning'][] = 'unhandled GUID "'.$this->GUIDname($NextObjectGUIDtext).'" {'.$NextObjectGUIDtext.'} in ASF body at offset '.($offset - 16 - 8); } else { - $ThisFileInfo['warning'][] = 'unknown GUID {'.$NextObjectGUIDtext.'} in ASF body at offset '.(ftell($fd) - 16 - 8); + $info['warning'][] = 'unknown GUID {'.$NextObjectGUIDtext.'} in ASF body at offset '.(ftell($this->getid3->fp) - 16 - 8); } - fseek($fd, ($NextObjectSize - 16 - 8), SEEK_CUR); + fseek($this->getid3->fp, ($NextObjectSize - 16 - 8), SEEK_CUR); break; } } @@ -1299,14 +1345,14 @@ class getid3_asf case 'WMV1': case 'WMV2': case 'WMV3': - case 'MSS1': - case 'MSS2': - case 'WMVA': - case 'WVC1': - case 'WMVP': - case 'WVP2': + case 'MSS1': + case 'MSS2': + case 'WMVA': + case 'WVC1': + case 'WMVP': + case 'WVP2': $thisfile_video['dataformat'] = 'wmv'; - $ThisFileInfo['mime_type'] = 'video/x-ms-wmv'; + $info['mime_type'] = 'video/x-ms-wmv'; break; case 'MP42': @@ -1314,7 +1360,7 @@ class getid3_asf case 'MP4S': case 'mp4s': $thisfile_video['dataformat'] = 'asf'; - $ThisFileInfo['mime_type'] = 'video/x-ms-asf'; + $info['mime_type'] = 'video/x-ms-asf'; break; default: @@ -1322,8 +1368,8 @@ class getid3_asf case 1: if (strstr($this->TrimConvert($streamdata['name']), 'Windows Media')) { $thisfile_video['dataformat'] = 'wmv'; - if ($ThisFileInfo['mime_type'] == 'video/x-ms-asf') { - $ThisFileInfo['mime_type'] = 'video/x-ms-wmv'; + if ($info['mime_type'] == 'video/x-ms-asf') { + $info['mime_type'] = 'video/x-ms-wmv'; } } break; @@ -1331,8 +1377,8 @@ class getid3_asf case 2: if (strstr($this->TrimConvert($streamdata['name']), 'Windows Media')) { $thisfile_audio['dataformat'] = 'wma'; - if ($ThisFileInfo['mime_type'] == 'video/x-ms-asf') { - $ThisFileInfo['mime_type'] = 'audio/x-ms-wma'; + if ($info['mime_type'] == 'video/x-ms-asf') { + $info['mime_type'] = 'audio/x-ms-wma'; } } break; @@ -1343,7 +1389,7 @@ class getid3_asf } } - switch (@$thisfile_audio['codec']) { + switch (isset($thisfile_audio['codec']) ? $thisfile_audio['codec'] : '') { case 'MPEG Layer-3': $thisfile_audio['dataformat'] = 'mp3'; break; @@ -1370,14 +1416,14 @@ class getid3_asf break; default: - $ThisFileInfo['warning'][] = 'Unknown streamtype: [codec_list_object][codec_entries]['.$streamnumber.'][type_raw] == '.$streamdata['type_raw']; + $info['warning'][] = 'Unknown streamtype: [codec_list_object][codec_entries]['.$streamnumber.'][type_raw] == '.$streamdata['type_raw']; break; } } } - if (isset($ThisFileInfo['audio'])) { + if (isset($info['audio'])) { $thisfile_audio['lossless'] = (isset($thisfile_audio['lossless']) ? $thisfile_audio['lossless'] : false); $thisfile_audio['dataformat'] = (!empty($thisfile_audio['dataformat']) ? $thisfile_audio['dataformat'] : 'asf'); } @@ -1396,16 +1442,16 @@ class getid3_asf } } } - $ThisFileInfo['bitrate'] = @$thisfile_audio['bitrate'] + @$thisfile_video['bitrate']; + $info['bitrate'] = (isset($thisfile_audio['bitrate']) ? $thisfile_audio['bitrate'] : 0) + (isset($thisfile_video['bitrate']) ? $thisfile_video['bitrate'] : 0); - if ((!isset($ThisFileInfo['playtime_seconds']) || ($ThisFileInfo['playtime_seconds'] <= 0)) && ($ThisFileInfo['bitrate'] > 0)) { - $ThisFileInfo['playtime_seconds'] = ($ThisFileInfo['filesize'] - $ThisFileInfo['avdataoffset']) / ($ThisFileInfo['bitrate'] / 8); + if ((!isset($info['playtime_seconds']) || ($info['playtime_seconds'] <= 0)) && ($info['bitrate'] > 0)) { + $info['playtime_seconds'] = ($info['filesize'] - $info['avdataoffset']) / ($info['bitrate'] / 8); } return true; } - function ASFCodecListObjectTypeLookup($CodecListType) { + static function ASFCodecListObjectTypeLookup($CodecListType) { static $ASFCodecListObjectTypeLookup = array(); if (empty($ASFCodecListObjectTypeLookup)) { $ASFCodecListObjectTypeLookup[0x0001] = 'Video Codec'; @@ -1416,128 +1462,129 @@ class getid3_asf return (isset($ASFCodecListObjectTypeLookup[$CodecListType]) ? $ASFCodecListObjectTypeLookup[$CodecListType] : 'Invalid Codec Type'); } - function KnownGUIDs() { - static $GUIDarray = array(); - if (empty($GUIDarray)) { - $GUIDarray['GETID3_ASF_Extended_Stream_Properties_Object'] = '14E6A5CB-C672-4332-8399-A96952065B5A'; - $GUIDarray['GETID3_ASF_Padding_Object'] = '1806D474-CADF-4509-A4BA-9AABCB96AAE8'; - $GUIDarray['GETID3_ASF_Payload_Ext_Syst_Pixel_Aspect_Ratio'] = '1B1EE554-F9EA-4BC8-821A-376B74E4C4B8'; - $GUIDarray['GETID3_ASF_Script_Command_Object'] = '1EFB1A30-0B62-11D0-A39B-00A0C90348F6'; - $GUIDarray['GETID3_ASF_No_Error_Correction'] = '20FB5700-5B55-11CF-A8FD-00805F5C442B'; - $GUIDarray['GETID3_ASF_Content_Branding_Object'] = '2211B3FA-BD23-11D2-B4B7-00A0C955FC6E'; - $GUIDarray['GETID3_ASF_Content_Encryption_Object'] = '2211B3FB-BD23-11D2-B4B7-00A0C955FC6E'; - $GUIDarray['GETID3_ASF_Digital_Signature_Object'] = '2211B3FC-BD23-11D2-B4B7-00A0C955FC6E'; - $GUIDarray['GETID3_ASF_Extended_Content_Encryption_Object'] = '298AE614-2622-4C17-B935-DAE07EE9289C'; - $GUIDarray['GETID3_ASF_Simple_Index_Object'] = '33000890-E5B1-11CF-89F4-00A0C90349CB'; - $GUIDarray['GETID3_ASF_Degradable_JPEG_Media'] = '35907DE0-E415-11CF-A917-00805F5C442B'; - $GUIDarray['GETID3_ASF_Payload_Extension_System_Timecode'] = '399595EC-8667-4E2D-8FDB-98814CE76C1E'; - $GUIDarray['GETID3_ASF_Binary_Media'] = '3AFB65E2-47EF-40F2-AC2C-70A90D71D343'; - $GUIDarray['GETID3_ASF_Timecode_Index_Object'] = '3CB73FD0-0C4A-4803-953D-EDF7B6228F0C'; - $GUIDarray['GETID3_ASF_Metadata_Library_Object'] = '44231C94-9498-49D1-A141-1D134E457054'; - $GUIDarray['GETID3_ASF_Reserved_3'] = '4B1ACBE3-100B-11D0-A39B-00A0C90348F6'; - $GUIDarray['GETID3_ASF_Reserved_4'] = '4CFEDB20-75F6-11CF-9C0F-00A0C90349CB'; - $GUIDarray['GETID3_ASF_Command_Media'] = '59DACFC0-59E6-11D0-A3AC-00A0C90348F6'; - $GUIDarray['GETID3_ASF_Header_Extension_Object'] = '5FBF03B5-A92E-11CF-8EE3-00C00C205365'; - $GUIDarray['GETID3_ASF_Media_Object_Index_Parameters_Obj'] = '6B203BAD-3F11-4E84-ACA8-D7613DE2CFA7'; - $GUIDarray['GETID3_ASF_Header_Object'] = '75B22630-668E-11CF-A6D9-00AA0062CE6C'; - $GUIDarray['GETID3_ASF_Content_Description_Object'] = '75B22633-668E-11CF-A6D9-00AA0062CE6C'; - $GUIDarray['GETID3_ASF_Error_Correction_Object'] = '75B22635-668E-11CF-A6D9-00AA0062CE6C'; - $GUIDarray['GETID3_ASF_Data_Object'] = '75B22636-668E-11CF-A6D9-00AA0062CE6C'; - $GUIDarray['GETID3_ASF_Web_Stream_Media_Subtype'] = '776257D4-C627-41CB-8F81-7AC7FF1C40CC'; - $GUIDarray['GETID3_ASF_Stream_Bitrate_Properties_Object'] = '7BF875CE-468D-11D1-8D82-006097C9A2B2'; - $GUIDarray['GETID3_ASF_Language_List_Object'] = '7C4346A9-EFE0-4BFC-B229-393EDE415C85'; - $GUIDarray['GETID3_ASF_Codec_List_Object'] = '86D15240-311D-11D0-A3A4-00A0C90348F6'; - $GUIDarray['GETID3_ASF_Reserved_2'] = '86D15241-311D-11D0-A3A4-00A0C90348F6'; - $GUIDarray['GETID3_ASF_File_Properties_Object'] = '8CABDCA1-A947-11CF-8EE4-00C00C205365'; - $GUIDarray['GETID3_ASF_File_Transfer_Media'] = '91BD222C-F21C-497A-8B6D-5AA86BFC0185'; - $GUIDarray['GETID3_ASF_Old_RTP_Extension_Data'] = '96800C63-4C94-11D1-837B-0080C7A37F95'; - $GUIDarray['GETID3_ASF_Advanced_Mutual_Exclusion_Object'] = 'A08649CF-4775-4670-8A16-6E35357566CD'; - $GUIDarray['GETID3_ASF_Bandwidth_Sharing_Object'] = 'A69609E6-517B-11D2-B6AF-00C04FD908E9'; - $GUIDarray['GETID3_ASF_Reserved_1'] = 'ABD3D211-A9BA-11cf-8EE6-00C00C205365'; - $GUIDarray['GETID3_ASF_Bandwidth_Sharing_Exclusive'] = 'AF6060AA-5197-11D2-B6AF-00C04FD908E9'; - $GUIDarray['GETID3_ASF_Bandwidth_Sharing_Partial'] = 'AF6060AB-5197-11D2-B6AF-00C04FD908E9'; - $GUIDarray['GETID3_ASF_JFIF_Media'] = 'B61BE100-5B4E-11CF-A8FD-00805F5C442B'; - $GUIDarray['GETID3_ASF_Stream_Properties_Object'] = 'B7DC0791-A9B7-11CF-8EE6-00C00C205365'; - $GUIDarray['GETID3_ASF_Video_Media'] = 'BC19EFC0-5B4D-11CF-A8FD-00805F5C442B'; - $GUIDarray['GETID3_ASF_Audio_Spread'] = 'BFC3CD50-618F-11CF-8BB2-00AA00B4E220'; - $GUIDarray['GETID3_ASF_Metadata_Object'] = 'C5F8CBEA-5BAF-4877-8467-AA8C44FA4CCA'; - $GUIDarray['GETID3_ASF_Payload_Ext_Syst_Sample_Duration'] = 'C6BD9450-867F-4907-83A3-C77921B733AD'; - $GUIDarray['GETID3_ASF_Group_Mutual_Exclusion_Object'] = 'D1465A40-5A79-4338-B71B-E36B8FD6C249'; - $GUIDarray['GETID3_ASF_Extended_Content_Description_Object'] = 'D2D0A440-E307-11D2-97F0-00A0C95EA850'; - $GUIDarray['GETID3_ASF_Stream_Prioritization_Object'] = 'D4FED15B-88D3-454F-81F0-ED5C45999E24'; - $GUIDarray['GETID3_ASF_Payload_Ext_System_Content_Type'] = 'D590DC20-07BC-436C-9CF7-F3BBFBF1A4DC'; - $GUIDarray['GETID3_ASF_Old_File_Properties_Object'] = 'D6E229D0-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_ASF_Header_Object'] = 'D6E229D1-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_ASF_Data_Object'] = 'D6E229D2-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Index_Object'] = 'D6E229D3-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Stream_Properties_Object'] = 'D6E229D4-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Content_Description_Object'] = 'D6E229D5-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Script_Command_Object'] = 'D6E229D6-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Marker_Object'] = 'D6E229D7-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Component_Download_Object'] = 'D6E229D8-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Stream_Group_Object'] = 'D6E229D9-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Scalable_Object'] = 'D6E229DA-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Prioritization_Object'] = 'D6E229DB-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Bitrate_Mutual_Exclusion_Object'] = 'D6E229DC-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Inter_Media_Dependency_Object'] = 'D6E229DD-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Rating_Object'] = 'D6E229DE-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Index_Parameters_Object'] = 'D6E229DF-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Color_Table_Object'] = 'D6E229E0-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Language_List_Object'] = 'D6E229E1-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Audio_Media'] = 'D6E229E2-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Video_Media'] = 'D6E229E3-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Image_Media'] = 'D6E229E4-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Timecode_Media'] = 'D6E229E5-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Text_Media'] = 'D6E229E6-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_MIDI_Media'] = 'D6E229E7-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Command_Media'] = 'D6E229E8-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_No_Error_Concealment'] = 'D6E229EA-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Scrambled_Audio'] = 'D6E229EB-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_No_Color_Table'] = 'D6E229EC-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_SMPTE_Time'] = 'D6E229ED-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_ASCII_Text'] = 'D6E229EE-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Unicode_Text'] = 'D6E229EF-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_HTML_Text'] = 'D6E229F0-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_URL_Command'] = 'D6E229F1-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Filename_Command'] = 'D6E229F2-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_ACM_Codec'] = 'D6E229F3-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_VCM_Codec'] = 'D6E229F4-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_QuickTime_Codec'] = 'D6E229F5-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_DirectShow_Transform_Filter'] = 'D6E229F6-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_DirectShow_Rendering_Filter'] = 'D6E229F7-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_No_Enhancement'] = 'D6E229F8-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Unknown_Enhancement_Type'] = 'D6E229F9-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Temporal_Enhancement'] = 'D6E229FA-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Spatial_Enhancement'] = 'D6E229FB-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Quality_Enhancement'] = 'D6E229FC-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Number_of_Channels_Enhancement'] = 'D6E229FD-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Frequency_Response_Enhancement'] = 'D6E229FE-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Media_Object'] = 'D6E229FF-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Mutex_Language'] = 'D6E22A00-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Mutex_Bitrate'] = 'D6E22A01-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Mutex_Unknown'] = 'D6E22A02-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_ASF_Placeholder_Object'] = 'D6E22A0E-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Data_Unit_Extension_Object'] = 'D6E22A0F-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Web_Stream_Format'] = 'DA1E6B13-8359-4050-B398-388E965BF00C'; - $GUIDarray['GETID3_ASF_Payload_Ext_System_File_Name'] = 'E165EC0E-19ED-45D7-B4A7-25CBD1E28E9B'; - $GUIDarray['GETID3_ASF_Marker_Object'] = 'F487CD01-A951-11CF-8EE6-00C00C205365'; - $GUIDarray['GETID3_ASF_Timecode_Index_Parameters_Object'] = 'F55E496D-9797-4B5D-8C8B-604DFE9BFB24'; - $GUIDarray['GETID3_ASF_Audio_Media'] = 'F8699E40-5B4D-11CF-A8FD-00805F5C442B'; - $GUIDarray['GETID3_ASF_Media_Object_Index_Object'] = 'FEB103F8-12AD-4C64-840F-2A1D2F7AD48C'; - $GUIDarray['GETID3_ASF_Alt_Extended_Content_Encryption_Obj'] = 'FF889EF1-ADEE-40DA-9E71-98704BB928CE'; - } + static function KnownGUIDs() { + static $GUIDarray = array( + 'GETID3_ASF_Extended_Stream_Properties_Object' => '14E6A5CB-C672-4332-8399-A96952065B5A', + 'GETID3_ASF_Padding_Object' => '1806D474-CADF-4509-A4BA-9AABCB96AAE8', + 'GETID3_ASF_Payload_Ext_Syst_Pixel_Aspect_Ratio' => '1B1EE554-F9EA-4BC8-821A-376B74E4C4B8', + 'GETID3_ASF_Script_Command_Object' => '1EFB1A30-0B62-11D0-A39B-00A0C90348F6', + 'GETID3_ASF_No_Error_Correction' => '20FB5700-5B55-11CF-A8FD-00805F5C442B', + 'GETID3_ASF_Content_Branding_Object' => '2211B3FA-BD23-11D2-B4B7-00A0C955FC6E', + 'GETID3_ASF_Content_Encryption_Object' => '2211B3FB-BD23-11D2-B4B7-00A0C955FC6E', + 'GETID3_ASF_Digital_Signature_Object' => '2211B3FC-BD23-11D2-B4B7-00A0C955FC6E', + 'GETID3_ASF_Extended_Content_Encryption_Object' => '298AE614-2622-4C17-B935-DAE07EE9289C', + 'GETID3_ASF_Simple_Index_Object' => '33000890-E5B1-11CF-89F4-00A0C90349CB', + 'GETID3_ASF_Degradable_JPEG_Media' => '35907DE0-E415-11CF-A917-00805F5C442B', + 'GETID3_ASF_Payload_Extension_System_Timecode' => '399595EC-8667-4E2D-8FDB-98814CE76C1E', + 'GETID3_ASF_Binary_Media' => '3AFB65E2-47EF-40F2-AC2C-70A90D71D343', + 'GETID3_ASF_Timecode_Index_Object' => '3CB73FD0-0C4A-4803-953D-EDF7B6228F0C', + 'GETID3_ASF_Metadata_Library_Object' => '44231C94-9498-49D1-A141-1D134E457054', + 'GETID3_ASF_Reserved_3' => '4B1ACBE3-100B-11D0-A39B-00A0C90348F6', + 'GETID3_ASF_Reserved_4' => '4CFEDB20-75F6-11CF-9C0F-00A0C90349CB', + 'GETID3_ASF_Command_Media' => '59DACFC0-59E6-11D0-A3AC-00A0C90348F6', + 'GETID3_ASF_Header_Extension_Object' => '5FBF03B5-A92E-11CF-8EE3-00C00C205365', + 'GETID3_ASF_Media_Object_Index_Parameters_Obj' => '6B203BAD-3F11-4E84-ACA8-D7613DE2CFA7', + 'GETID3_ASF_Header_Object' => '75B22630-668E-11CF-A6D9-00AA0062CE6C', + 'GETID3_ASF_Content_Description_Object' => '75B22633-668E-11CF-A6D9-00AA0062CE6C', + 'GETID3_ASF_Error_Correction_Object' => '75B22635-668E-11CF-A6D9-00AA0062CE6C', + 'GETID3_ASF_Data_Object' => '75B22636-668E-11CF-A6D9-00AA0062CE6C', + 'GETID3_ASF_Web_Stream_Media_Subtype' => '776257D4-C627-41CB-8F81-7AC7FF1C40CC', + 'GETID3_ASF_Stream_Bitrate_Properties_Object' => '7BF875CE-468D-11D1-8D82-006097C9A2B2', + 'GETID3_ASF_Language_List_Object' => '7C4346A9-EFE0-4BFC-B229-393EDE415C85', + 'GETID3_ASF_Codec_List_Object' => '86D15240-311D-11D0-A3A4-00A0C90348F6', + 'GETID3_ASF_Reserved_2' => '86D15241-311D-11D0-A3A4-00A0C90348F6', + 'GETID3_ASF_File_Properties_Object' => '8CABDCA1-A947-11CF-8EE4-00C00C205365', + 'GETID3_ASF_File_Transfer_Media' => '91BD222C-F21C-497A-8B6D-5AA86BFC0185', + 'GETID3_ASF_Old_RTP_Extension_Data' => '96800C63-4C94-11D1-837B-0080C7A37F95', + 'GETID3_ASF_Advanced_Mutual_Exclusion_Object' => 'A08649CF-4775-4670-8A16-6E35357566CD', + 'GETID3_ASF_Bandwidth_Sharing_Object' => 'A69609E6-517B-11D2-B6AF-00C04FD908E9', + 'GETID3_ASF_Reserved_1' => 'ABD3D211-A9BA-11cf-8EE6-00C00C205365', + 'GETID3_ASF_Bandwidth_Sharing_Exclusive' => 'AF6060AA-5197-11D2-B6AF-00C04FD908E9', + 'GETID3_ASF_Bandwidth_Sharing_Partial' => 'AF6060AB-5197-11D2-B6AF-00C04FD908E9', + 'GETID3_ASF_JFIF_Media' => 'B61BE100-5B4E-11CF-A8FD-00805F5C442B', + 'GETID3_ASF_Stream_Properties_Object' => 'B7DC0791-A9B7-11CF-8EE6-00C00C205365', + 'GETID3_ASF_Video_Media' => 'BC19EFC0-5B4D-11CF-A8FD-00805F5C442B', + 'GETID3_ASF_Audio_Spread' => 'BFC3CD50-618F-11CF-8BB2-00AA00B4E220', + 'GETID3_ASF_Metadata_Object' => 'C5F8CBEA-5BAF-4877-8467-AA8C44FA4CCA', + 'GETID3_ASF_Payload_Ext_Syst_Sample_Duration' => 'C6BD9450-867F-4907-83A3-C77921B733AD', + 'GETID3_ASF_Group_Mutual_Exclusion_Object' => 'D1465A40-5A79-4338-B71B-E36B8FD6C249', + 'GETID3_ASF_Extended_Content_Description_Object' => 'D2D0A440-E307-11D2-97F0-00A0C95EA850', + 'GETID3_ASF_Stream_Prioritization_Object' => 'D4FED15B-88D3-454F-81F0-ED5C45999E24', + 'GETID3_ASF_Payload_Ext_System_Content_Type' => 'D590DC20-07BC-436C-9CF7-F3BBFBF1A4DC', + 'GETID3_ASF_Old_File_Properties_Object' => 'D6E229D0-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_ASF_Header_Object' => 'D6E229D1-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_ASF_Data_Object' => 'D6E229D2-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Index_Object' => 'D6E229D3-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Stream_Properties_Object' => 'D6E229D4-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Content_Description_Object' => 'D6E229D5-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Script_Command_Object' => 'D6E229D6-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Marker_Object' => 'D6E229D7-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Component_Download_Object' => 'D6E229D8-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Stream_Group_Object' => 'D6E229D9-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Scalable_Object' => 'D6E229DA-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Prioritization_Object' => 'D6E229DB-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Bitrate_Mutual_Exclusion_Object' => 'D6E229DC-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Inter_Media_Dependency_Object' => 'D6E229DD-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Rating_Object' => 'D6E229DE-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Index_Parameters_Object' => 'D6E229DF-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Color_Table_Object' => 'D6E229E0-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Language_List_Object' => 'D6E229E1-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Audio_Media' => 'D6E229E2-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Video_Media' => 'D6E229E3-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Image_Media' => 'D6E229E4-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Timecode_Media' => 'D6E229E5-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Text_Media' => 'D6E229E6-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_MIDI_Media' => 'D6E229E7-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Command_Media' => 'D6E229E8-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_No_Error_Concealment' => 'D6E229EA-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Scrambled_Audio' => 'D6E229EB-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_No_Color_Table' => 'D6E229EC-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_SMPTE_Time' => 'D6E229ED-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_ASCII_Text' => 'D6E229EE-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Unicode_Text' => 'D6E229EF-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_HTML_Text' => 'D6E229F0-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_URL_Command' => 'D6E229F1-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Filename_Command' => 'D6E229F2-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_ACM_Codec' => 'D6E229F3-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_VCM_Codec' => 'D6E229F4-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_QuickTime_Codec' => 'D6E229F5-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_DirectShow_Transform_Filter' => 'D6E229F6-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_DirectShow_Rendering_Filter' => 'D6E229F7-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_No_Enhancement' => 'D6E229F8-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Unknown_Enhancement_Type' => 'D6E229F9-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Temporal_Enhancement' => 'D6E229FA-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Spatial_Enhancement' => 'D6E229FB-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Quality_Enhancement' => 'D6E229FC-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Number_of_Channels_Enhancement' => 'D6E229FD-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Frequency_Response_Enhancement' => 'D6E229FE-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Media_Object' => 'D6E229FF-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Mutex_Language' => 'D6E22A00-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Mutex_Bitrate' => 'D6E22A01-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Mutex_Unknown' => 'D6E22A02-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_ASF_Placeholder_Object' => 'D6E22A0E-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Data_Unit_Extension_Object' => 'D6E22A0F-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Web_Stream_Format' => 'DA1E6B13-8359-4050-B398-388E965BF00C', + 'GETID3_ASF_Payload_Ext_System_File_Name' => 'E165EC0E-19ED-45D7-B4A7-25CBD1E28E9B', + 'GETID3_ASF_Marker_Object' => 'F487CD01-A951-11CF-8EE6-00C00C205365', + 'GETID3_ASF_Timecode_Index_Parameters_Object' => 'F55E496D-9797-4B5D-8C8B-604DFE9BFB24', + 'GETID3_ASF_Audio_Media' => 'F8699E40-5B4D-11CF-A8FD-00805F5C442B', + 'GETID3_ASF_Media_Object_Index_Object' => 'FEB103F8-12AD-4C64-840F-2A1D2F7AD48C', + 'GETID3_ASF_Alt_Extended_Content_Encryption_Obj' => 'FF889EF1-ADEE-40DA-9E71-98704BB928CE', + 'GETID3_ASF_Index_Placeholder_Object' => 'D9AADE20-7C17-4F9C-BC28-8555DD98E2A2', // http://cpan.uwinnipeg.ca/htdocs/Audio-WMA/Audio/WMA.pm.html + 'GETID3_ASF_Compatibility_Object' => '26F18B5D-4584-47EC-9F5F-0E651F0452C9', // http://cpan.uwinnipeg.ca/htdocs/Audio-WMA/Audio/WMA.pm.html + ); return $GUIDarray; } - function GUIDname($GUIDstring) { + static function GUIDname($GUIDstring) { static $GUIDarray = array(); if (empty($GUIDarray)) { - $GUIDarray = $this->KnownGUIDs(); + $GUIDarray = getid3_asf::KnownGUIDs(); } return array_search($GUIDstring, $GUIDarray); } - function ASFIndexObjectIndexTypeLookup($id) { + static function ASFIndexObjectIndexTypeLookup($id) { static $ASFIndexObjectIndexTypeLookup = array(); if (empty($ASFIndexObjectIndexTypeLookup)) { $ASFIndexObjectIndexTypeLookup[1] = 'Nearest Past Data Packet'; @@ -1547,7 +1594,7 @@ class getid3_asf return (isset($ASFIndexObjectIndexTypeLookup[$id]) ? $ASFIndexObjectIndexTypeLookup[$id] : 'invalid'); } - function GUIDtoBytestring($GUIDstring) { + static function GUIDtoBytestring($GUIDstring) { // Microsoft defines these 16-byte (128-bit) GUIDs in the strangest way: // first 4 bytes are in little-endian order // next 2 bytes are appended in little-endian order @@ -1582,7 +1629,7 @@ class getid3_asf return $hexbytecharstring; } - function BytestringToGUID($Bytestring) { + static function BytestringToGUID($Bytestring) { $GUIDstring = str_pad(dechex(ord($Bytestring{3})), 2, '0', STR_PAD_LEFT); $GUIDstring .= str_pad(dechex(ord($Bytestring{2})), 2, '0', STR_PAD_LEFT); $GUIDstring .= str_pad(dechex(ord($Bytestring{1})), 2, '0', STR_PAD_LEFT); @@ -1607,7 +1654,7 @@ class getid3_asf return strtoupper($GUIDstring); } - function FILETIMEtoUNIXtime($FILETIME, $round=true) { + static function FILETIMEtoUNIXtime($FILETIME, $round=true) { // FILETIME is a 64-bit unsigned integer representing // the number of 100-nanosecond intervals since January 1, 1601 // UNIX timestamp is number of seconds since January 1, 1970 @@ -1618,7 +1665,7 @@ class getid3_asf return ($FILETIME - 116444736000000000) / 10000000; } - function WMpictureTypeLookup($WMpictureType) { + static function WMpictureTypeLookup($WMpictureType) { static $WMpictureTypeLookup = array(); if (empty($WMpictureTypeLookup)) { $WMpictureTypeLookup[0x03] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Front Cover'); @@ -1640,34 +1687,335 @@ class getid3_asf $WMpictureTypeLookup[0x13] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Band Logotype'); $WMpictureTypeLookup[0x14] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Publisher Logotype'); } - return @$WMpictureTypeLookup[$WMpictureType]; + return (isset($WMpictureTypeLookup[$WMpictureType]) ? $WMpictureTypeLookup[$WMpictureType] : ''); } + function ASF_HeaderExtensionObjectDataParse(&$asf_header_extension_object_data, &$unhandled_sections) { + // http://msdn.microsoft.com/en-us/library/bb643323.aspx - // Remove terminator 00 00 and convert UNICODE to Latin-1 - function TrimConvert($string) { + $offset = 0; + $objectOffset = 0; + $HeaderExtensionObjectParsed = array(); + while ($objectOffset < strlen($asf_header_extension_object_data)) { + $offset = $objectOffset; + $thisObject = array(); - // remove terminator, only if present (it should be, but...) - if (substr($string, strlen($string) - 2, 2) == "\x00\x00") { - $string = substr($string, 0, strlen($string) - 2); + $thisObject['guid'] = substr($asf_header_extension_object_data, $offset, 16); + $offset += 16; + $thisObject['guid_text'] = $this->BytestringToGUID($thisObject['guid']); + $thisObject['guid_name'] = $this->GUIDname($thisObject['guid_text']); + + $thisObject['size'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 8)); + $offset += 8; + if ($thisObject['size'] <= 0) { + break; + } + + switch ($thisObject['guid']) { + case GETID3_ASF_Extended_Stream_Properties_Object: + $thisObject['start_time'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 8)); + $offset += 8; + $thisObject['start_time_unix'] = $this->FILETIMEtoUNIXtime($thisObject['start_time']); + + $thisObject['end_time'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 8)); + $offset += 8; + $thisObject['end_time_unix'] = $this->FILETIMEtoUNIXtime($thisObject['end_time']); + + $thisObject['data_bitrate'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); + $offset += 4; + + $thisObject['buffer_size'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); + $offset += 4; + + $thisObject['initial_buffer_fullness'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); + $offset += 4; + + $thisObject['alternate_data_bitrate'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); + $offset += 4; + + $thisObject['alternate_buffer_size'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); + $offset += 4; + + $thisObject['alternate_initial_buffer_fullness'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); + $offset += 4; + + $thisObject['maximum_object_size'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); + $offset += 4; + + $thisObject['flags_raw'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); + $offset += 4; + $thisObject['flags']['reliable'] = (bool) $thisObject['flags_raw'] & 0x00000001; + $thisObject['flags']['seekable'] = (bool) $thisObject['flags_raw'] & 0x00000002; + $thisObject['flags']['no_cleanpoints'] = (bool) $thisObject['flags_raw'] & 0x00000004; + $thisObject['flags']['resend_live_cleanpoints'] = (bool) $thisObject['flags_raw'] & 0x00000008; + + $thisObject['stream_number'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + $thisObject['stream_language_id_index'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + $thisObject['average_time_per_frame'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); + $offset += 4; + + $thisObject['stream_name_count'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + $thisObject['payload_extension_system_count'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + for ($i = 0; $i < $thisObject['stream_name_count']; $i++) { + $streamName = array(); + + $streamName['language_id_index'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + $streamName['stream_name_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + $streamName['stream_name'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, $streamName['stream_name_length'])); + $offset += $streamName['stream_name_length']; + + $thisObject['stream_names'][$i] = $streamName; + } + + for ($i = 0; $i < $thisObject['payload_extension_system_count']; $i++) { + $payloadExtensionSystem = array(); + + $payloadExtensionSystem['extension_system_id'] = substr($asf_header_extension_object_data, $offset, 16); + $offset += 16; + $payloadExtensionSystem['extension_system_id_text'] = $this->BytestringToGUID($payloadExtensionSystem['extension_system_id']); + + $payloadExtensionSystem['extension_system_size'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + if ($payloadExtensionSystem['extension_system_size'] <= 0) { + break 2; + } + + $payloadExtensionSystem['extension_system_info_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); + $offset += 4; + + $payloadExtensionSystem['extension_system_info_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, $payloadExtensionSystem['extension_system_info_length'])); + $offset += $payloadExtensionSystem['extension_system_info_length']; + + $thisObject['payload_extension_systems'][$i] = $payloadExtensionSystem; + } + + break; + + case GETID3_ASF_Padding_Object: + // padding, skip it + break; + + case GETID3_ASF_Metadata_Object: + $thisObject['description_record_counts'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + for ($i = 0; $i < $thisObject['description_record_counts']; $i++) { + $descriptionRecord = array(); + + $descriptionRecord['reserved_1'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); // must be zero + $offset += 2; + + $descriptionRecord['stream_number'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + $descriptionRecord['name_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + $descriptionRecord['data_type'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + $descriptionRecord['data_type_text'] = $this->ASFmetadataLibraryObjectDataTypeLookup($descriptionRecord['data_type']); + + $descriptionRecord['data_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); + $offset += 4; + + $descriptionRecord['name'] = substr($asf_header_extension_object_data, $offset, $descriptionRecord['name_length']); + $offset += $descriptionRecord['name_length']; + + $descriptionRecord['data'] = substr($asf_header_extension_object_data, $offset, $descriptionRecord['data_length']); + $offset += $descriptionRecord['data_length']; + switch ($descriptionRecord['data_type']) { + case 0x0000: // Unicode string + break; + + case 0x0001: // BYTE array + // do nothing + break; + + case 0x0002: // BOOL + $descriptionRecord['data'] = (bool) getid3_lib::LittleEndian2Int($descriptionRecord['data']); + break; + + case 0x0003: // DWORD + case 0x0004: // QWORD + case 0x0005: // WORD + $descriptionRecord['data'] = getid3_lib::LittleEndian2Int($descriptionRecord['data']); + break; + + case 0x0006: // GUID + $descriptionRecord['data_text'] = $this->BytestringToGUID($descriptionRecord['data']); + break; + } + + $thisObject['description_record'][$i] = $descriptionRecord; + } + break; + + case GETID3_ASF_Language_List_Object: + $thisObject['language_id_record_counts'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + for ($i = 0; $i < $thisObject['language_id_record_counts']; $i++) { + $languageIDrecord = array(); + + $languageIDrecord['language_id_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 1)); + $offset += 1; + + $languageIDrecord['language_id'] = substr($asf_header_extension_object_data, $offset, $languageIDrecord['language_id_length']); + $offset += $languageIDrecord['language_id_length']; + + $thisObject['language_id_record'][$i] = $languageIDrecord; + } + break; + + case GETID3_ASF_Metadata_Library_Object: + $thisObject['description_records_count'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + for ($i = 0; $i < $thisObject['description_records_count']; $i++) { + $descriptionRecord = array(); + + $descriptionRecord['language_list_index'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + $descriptionRecord['stream_number'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + $descriptionRecord['name_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + $descriptionRecord['data_type'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + $descriptionRecord['data_type_text'] = $this->ASFmetadataLibraryObjectDataTypeLookup($descriptionRecord['data_type']); + + $descriptionRecord['data_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); + $offset += 4; + + $descriptionRecord['name'] = substr($asf_header_extension_object_data, $offset, $descriptionRecord['name_length']); + $offset += $descriptionRecord['name_length']; + + $descriptionRecord['data'] = substr($asf_header_extension_object_data, $offset, $descriptionRecord['data_length']); + $offset += $descriptionRecord['data_length']; + + if (preg_match('#^WM/Picture$#', str_replace("\x00", '', trim($descriptionRecord['name'])))) { + $WMpicture = $this->ASF_WMpicture($descriptionRecord['data']); + foreach ($WMpicture as $key => $value) { + $descriptionRecord['data'] = $WMpicture; + } + unset($WMpicture); + } + + $thisObject['description_record'][$i] = $descriptionRecord; + } + break; + + default: + $unhandled_sections++; + if ($this->GUIDname($thisObject['guid_text'])) { + $this->getid3->info['warning'][] = 'unhandled Header Extension Object GUID "'.$this->GUIDname($thisObject['guid_text']).'" {'.$thisObject['guid_text'].'} at offset '.($offset - 16 - 8); + } else { + $this->getid3->info['warning'][] = 'unknown Header Extension Object GUID {'.$thisObject['guid_text'].'} in at offset '.($offset - 16 - 8); + } + break; + } + $HeaderExtensionObjectParsed[] = $thisObject; + + $objectOffset += $thisObject['size']; } - - // convert - return trim(getid3_lib::iconv_fallback('UTF-16LE', 'ISO-8859-1', $string), ' '); + return $HeaderExtensionObjectParsed; } - function TrimTerm($string) { + static function ASFmetadataLibraryObjectDataTypeLookup($id) { + static $ASFmetadataLibraryObjectDataTypeLookup = array( + 0x0000 => 'Unicode string', // The data consists of a sequence of Unicode characters + 0x0001 => 'BYTE array', // The type of the data is implementation-specific + 0x0002 => 'BOOL', // The data is 2 bytes long and should be interpreted as a 16-bit unsigned integer. Only 0x0000 or 0x0001 are permitted values + 0x0003 => 'DWORD', // The data is 4 bytes long and should be interpreted as a 32-bit unsigned integer + 0x0004 => 'QWORD', // The data is 8 bytes long and should be interpreted as a 64-bit unsigned integer + 0x0005 => 'WORD', // The data is 2 bytes long and should be interpreted as a 16-bit unsigned integer + 0x0006 => 'GUID', // The data is 16 bytes long and should be interpreted as a 128-bit GUID + ); + return (isset($ASFmetadataLibraryObjectDataTypeLookup[$id]) ? $ASFmetadataLibraryObjectDataTypeLookup[$id] : 'invalid'); + } + function ASF_WMpicture(&$data) { + //typedef struct _WMPicture{ + // LPWSTR pwszMIMEType; + // BYTE bPictureType; + // LPWSTR pwszDescription; + // DWORD dwDataLen; + // BYTE* pbData; + //} WM_PICTURE; + + $WMpicture = array(); + + $offset = 0; + $WMpicture['image_type_id'] = getid3_lib::LittleEndian2Int(substr($data, $offset, 1)); + $offset += 1; + $WMpicture['image_type'] = $this->WMpictureTypeLookup($WMpicture['image_type_id']); + $WMpicture['image_size'] = getid3_lib::LittleEndian2Int(substr($data, $offset, 4)); + $offset += 4; + + $WMpicture['image_mime'] = ''; + do { + $next_byte_pair = substr($data, $offset, 2); + $offset += 2; + $WMpicture['image_mime'] .= $next_byte_pair; + } while ($next_byte_pair !== "\x00\x00"); + + $WMpicture['image_description'] = ''; + do { + $next_byte_pair = substr($data, $offset, 2); + $offset += 2; + $WMpicture['image_description'] .= $next_byte_pair; + } while ($next_byte_pair !== "\x00\x00"); + + $WMpicture['dataoffset'] = $offset; + $WMpicture['data'] = substr($data, $offset); + + $imageinfo = array(); + $WMpicture['image_mime'] = ''; + $imagechunkcheck = getid3_lib::GetDataImageSize($WMpicture['data'], $imageinfo); + unset($imageinfo); + if (!empty($imagechunkcheck)) { + $WMpicture['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]); + } + if (!isset($this->getid3->info['asf']['comments']['picture'])) { + $this->getid3->info['asf']['comments']['picture'] = array(); + } + $this->getid3->info['asf']['comments']['picture'][] = array('data'=>$WMpicture['data'], 'image_mime'=>$WMpicture['image_mime']); + + return $WMpicture; + } + + + // Remove terminator 00 00 and convert UTF-16LE to Latin-1 + static function TrimConvert($string) { + return trim(getid3_lib::iconv_fallback('UTF-16LE', 'ISO-8859-1', getid3_asf::TrimTerm($string)), ' '); + } + + + // Remove terminator 00 00 + static function TrimTerm($string) { // remove terminator, only if present (it should be, but...) - if (substr($string, -2) == "\x00\x00") { + if (substr($string, -2) === "\x00\x00") { $string = substr($string, 0, -2); } return $string; } - } - ?> \ No newline at end of file diff --git a/3rdparty/getid3/module.audio-video.bink.php b/3rdparty/getid3/module.audio-video.bink.php new file mode 100644 index 0000000000..0a32139676 --- /dev/null +++ b/3rdparty/getid3/module.audio-video.bink.php @@ -0,0 +1,73 @@ + // +// 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 +{ + + 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; + + } + + 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; + } + + function ParseSmacker() { + $info = &$this->getid3->info; + $info['fileformat'] = 'smacker'; + $info['video']['dataformat'] = 'smacker'; + + return true; + } + +} + +?> \ No newline at end of file diff --git a/3rdparty/getid3/module.audio-video.flv.php b/3rdparty/getid3/module.audio-video.flv.php new file mode 100644 index 0000000000..ba3cd908df --- /dev/null +++ b/3rdparty/getid3/module.audio-video.flv.php @@ -0,0 +1,731 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +// // +// FLV module by Seth Kaufman // +// // +// * version 0.1 (26 June 2005) // +// // +// // +// * version 0.1.1 (15 July 2005) // +// minor modifications by James Heinrich // +// // +// * version 0.2 (22 February 2006) // +// Support for On2 VP6 codec and meta information // +// by Steve Webster // +// // +// * version 0.3 (15 June 2006) // +// Modified to not read entire file into memory // +// by James Heinrich // +// // +// * version 0.4 (07 December 2007) // +// Bugfixes for incorrectly parsed FLV dimensions // +// and incorrect parsing of onMetaTag // +// by Evgeny Moysevich // +// // +// * 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 // +// // +// * version 0.6 (24 May 2009) // +// Better parsing of files with h264 video // +// by Evgeny Moysevich // +// // +// * 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 +{ + var $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 + + 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; + } + + + 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); + } + + function FLVaudioRate($id) { + $FLVaudioRate = array( + 0 => 5500, + 1 => 11025, + 2 => 22050, + 3 => 44100, + ); + return (isset($FLVaudioRate[$id]) ? $FLVaudioRate[$id] : false); + } + + function FLVaudioBitDepth($id) { + $FLVaudioBitDepth = array( + 0 => 8, + 1 => 16, + ); + return (isset($FLVaudioBitDepth[$id]) ? $FLVaudioBitDepth[$id] : false); + } + + 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 { + var $bytes; + var $pos; + + function AMFStream(&$bytes) { + $this->bytes =& $bytes; + $this->pos = 0; + } + + function readByte() { + return getid3_lib::BigEndian2Int(substr($this->bytes, $this->pos++, 1)); + } + + function readInt() { + return ($this->readByte() << 8) + $this->readByte(); + } + + function readLong() { + return ($this->readByte() << 24) + ($this->readByte() << 16) + ($this->readByte() << 8) + $this->readByte(); + } + + function readDouble() { + return getid3_lib::BigEndian2Float($this->read(8)); + } + + function readUTF() { + $length = $this->readInt(); + return $this->read($length); + } + + function readLongUTF() { + $length = $this->readLong(); + return $this->read($length); + } + + function read($length) { + $val = substr($this->bytes, $this->pos, $length); + $this->pos += $length; + return $val; + } + + function peekByte() { + $pos = $this->pos; + $val = $this->readByte(); + $this->pos = $pos; + return $val; + } + + function peekInt() { + $pos = $this->pos; + $val = $this->readInt(); + $this->pos = $pos; + return $val; + } + + function peekLong() { + $pos = $this->pos; + $val = $this->readLong(); + $this->pos = $pos; + return $val; + } + + function peekDouble() { + $pos = $this->pos; + $val = $this->readDouble(); + $this->pos = $pos; + return $val; + } + + function peekUTF() { + $pos = $this->pos; + $val = $this->readUTF(); + $this->pos = $pos; + return $val; + } + + function peekLongUTF() { + $pos = $this->pos; + $val = $this->readLongUTF(); + $this->pos = $pos; + return $val; + } +} + +class AMFReader { + var $stream; + + function AMFReader(&$stream) { + $this->stream =& $stream; + } + + 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; + } + + function readDouble() { + return $this->stream->readDouble(); + } + + function readBoolean() { + return $this->stream->readByte() == 1; + } + + function readString() { + return $this->stream->readUTF(); + } + + 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; + } + + 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; + } + + function readArray() { + $length = $this->stream->readLong(); + $data = array(); + + for ($i = 0; $i < $length; $i++) { + $data[] = $this->readData(); + } + return $data; + } + + function readDate() { + $timestamp = $this->stream->readDouble(); + $timezone = $this->stream->readInt(); + return $timestamp; + } + + function readLongString() { + return $this->stream->readLongUTF(); + } + + function readXML() { + return $this->stream->readLongUTF(); + } + + function readTypedObject() { + $className = $this->stream->readUTF(); + return $this->readObject(); + } +} + +class AVCSequenceParameterSetReader { + var $sps; + var $start = 0; + var $currentBytes = 0; + var $currentBits = 0; + var $width; + var $height; + + function AVCSequenceParameterSetReader($sps) { + $this->sps = $sps; + } + + 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; + } + + function skipBits($bits) { + $newBits = $this->currentBits + $bits; + $this->currentBytes += (int)floor($newBits / 8); + $this->currentBits = $newBits % 8; + } + + function getBit() { + $result = (getid3_lib::BigEndian2Int(substr($this->sps, $this->currentBytes, 1)) >> (7 - $this->currentBits)) & 0x01; + $this->skipBits(1); + return $result; + } + + function getBits($bits) { + $result = 0; + for ($i = 0; $i < $bits; $i++) { + $result = ($result << 1) + $this->getBit(); + } + return $result; + } + + 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; + } + + function expGolombSe() { + $result = $this->expGolombUe(); + if (($result & 0x01) == 0) { + return -($result >> 1); + } else { + return ($result + 1) >> 1; + } + } + + function getWidth() { + return $this->width; + } + + function getHeight() { + return $this->height; + } +} + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/module.audio-video.matroska.php b/3rdparty/getid3/module.audio-video.matroska.php similarity index 53% rename from apps/media/getID3/getid3/module.audio-video.matroska.php rename to 3rdparty/getid3/module.audio-video.matroska.php index 004056edb0..b7ab667989 100644 --- a/apps/media/getID3/getid3/module.audio-video.matroska.php +++ b/3rdparty/getid3/module.audio-video.matroska.php @@ -14,9 +14,10 @@ ///////////////////////////////////////////////////////////////// +// from: http://www.matroska.org/technical/specs/index.html define('EBML_ID_CHAPTERS', 0x0043A770); // [10][43][A7][70] -- A system to define basic menus and partition data. For more detailed information, look at the Chapters Explanation. define('EBML_ID_SEEKHEAD', 0x014D9B74); // [11][4D][9B][74] -- Contains the position of other level 1 elements. -define('EBML_ID_TAGS', 0x0254C367); // [12][54][C3][67] -- Element containing elements specific to Tracks/Chapters. A list of valid tags can be found here. +define('EBML_ID_TAGS', 0x0254C367); // [12][54][C3][67] -- Element containing elements specific to Tracks/Chapters. A list of valid tags can be found . define('EBML_ID_INFO', 0x0549A966); // [15][49][A9][66] -- Contains miscellaneous general information and statistics on the file. define('EBML_ID_TRACKS', 0x0654AE6B); // [16][54][AE][6B] -- A top-level block of information with many tracks described. define('EBML_ID_SEGMENT', 0x08538067); // [18][53][80][67] -- This element contains all other top-level (level 1) elements. Typically a Matroska file is composed of 1 segment. @@ -111,7 +112,7 @@ define('EBML_ID_TARGETS', 0x23C0); // [63][C0] -- define('EBML_ID_CHAPTERPHYSICALEQUIV', 0x23C3); // [63][C3] -- Specify the physical equivalent of this ChapterAtom like "DVD" (60) or "SIDE" (50), see complete list of values. define('EBML_ID_TAGCHAPTERUID', 0x23C4); // [63][C4] -- A unique ID to identify the Chapter(s) the tags belong to. If the value is 0 at this level, the tags apply to all chapters in the Segment. define('EBML_ID_TAGTRACKUID', 0x23C5); // [63][C5] -- A unique ID to identify the Track(s) the tags belong to. If the value is 0 at this level, the tags apply to all tracks in the Segment. -define('EBML_ID_ATTACHMENTUID', 0x23C6); // [63][C6] -- A unique ID to identify the Attachment(s) the tags belong to. If the value is 0 at this level, the tags apply to all the attachments in the Segment. +define('EBML_ID_TAGATTACHMENTUID', 0x23C6); // [63][C6] -- A unique ID to identify the Attachment(s) the tags belong to. If the value is 0 at this level, the tags apply to all the attachments in the Segment. define('EBML_ID_TAGEDITIONUID', 0x23C9); // [63][C9] -- A unique ID to identify the EditionEntry(s) the tags belong to. If the value is 0 at this level, the tags apply to all editions in the Segment. define('EBML_ID_TARGETTYPE', 0x23CA); // [63][CA] -- An informational string that can be used to display the logical level of the target like "ALBUM", "TRACK", "MOVIE", "CHAPTER", etc (see TargetType). define('EBML_ID_TRACKTRANSLATE', 0x2624); // [66][24] -- The track identification for the given Chapter Codec. @@ -205,176 +206,367 @@ define('EBML_ID_CLUSTERREFERENCEBLOCK', 0x7B); // [FB] -- define('EBML_ID_CLUSTERREFERENCEVIRTUAL', 0x7D); // [FD] -- Relative position of the data that should be in position of the virtual block. -class getid3_matroska +class getid3_matroska extends getid3_handler { - var $read_buffer_size = 32768; // size of read buffer, 32kB is default - var $hide_clusters = true; // if true, do not return information about CLUSTER chunks, since there's a lot of them and they're not usually useful - var $warnings = array(); + // public options + public static $hide_clusters = true; // if true, do not return information about CLUSTER chunks, since there's a lot of them and they're not usually useful [default: TRUE] + public static $parse_whole_file = false; // true to parse the whole file, not only header [default: FALSE] - function getid3_matroska(&$fd, &$ThisFileInfo) { + // private parser settings/placeholders + private $EBMLbuffer = ''; + private $EBMLbuffer_offset = 0; + private $EBMLbuffer_length = 0; + private $current_offset = 0; + private $unuseful_elements = array(EBML_ID_CRC32, EBML_ID_VOID); + + public function Analyze() + { + $info = &$this->getid3->info; - // http://www.matroska.org/technical/specs/index.html#EBMLBasics - $offset = $ThisFileInfo['avdataoffset']; - $EBMLdata = ''; - $EBMLdata_offset = $offset; - - if ($ThisFileInfo['avdataend'] > 2147483648) { - $this->warnings[] = 'This version of getID3() may or may not correctly handle Matroska files larger than 2GB'; + // parse container + try { + $this->parseEBML($info); + } + catch (Exception $e) { + $info['error'][] = 'EBML parser: '.$e->getMessage(); } - while ($offset < $ThisFileInfo['avdataend']) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - - $top_element_offset = $offset; - $top_element_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $top_element_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - if ($top_element_length === false) { - $this->warnings[] = 'invalid chunk length at '.$top_element_offset; - $offset = pow(2, 63); - break; + // calculate playtime + if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) { + foreach ($info['matroska']['info'] as $key => $infoarray) { + if (isset($infoarray['Duration'])) { + // TimecodeScale is how many nanoseconds each Duration unit is + $info['playtime_seconds'] = $infoarray['Duration'] * ((isset($infoarray['TimecodeScale']) ? $infoarray['TimecodeScale'] : 1000000) / 1000000000); + break; + } } - $top_element_endoffset = $offset + $top_element_length; - switch ($top_element_id) { + } + + // extract tags + if (isset($info['matroska']['tags']) && is_array($info['matroska']['tags'])) { + foreach ($info['matroska']['tags'] as $key => $infoarray) { + $this->ExtractCommentsSimpleTag($infoarray); + } + } + + // process tracks + if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) { + foreach ($info['matroska']['tracks']['tracks'] as $key => $trackarray) { + + $track_info = array(); + $track_info['dataformat'] = self::MatroskaCodecIDtoCommonName($trackarray['CodecID']); + $track_info['default'] = (isset($trackarray['FlagDefault']) ? $trackarray['FlagDefault'] : true); + if (isset($trackarray['Name'])) { $track_info['name'] = $trackarray['Name']; } + + switch ($trackarray['TrackType']) { + + case 1: // Video + $track_info['resolution_x'] = $trackarray['PixelWidth']; + $track_info['resolution_y'] = $trackarray['PixelHeight']; + if (isset($trackarray['DisplayWidth'])) { $track_info['display_x'] = $trackarray['DisplayWidth']; } + if (isset($trackarray['DisplayHeight'])) { $track_info['display_y'] = $trackarray['DisplayHeight']; } + if (isset($trackarray['DefaultDuration'])) { $track_info['frame_rate'] = round(1000000000 / $trackarray['DefaultDuration'], 3); } + //if (isset($trackarray['CodecName'])) { $track_info['codec'] = $trackarray['CodecName']; } + + switch ($trackarray['CodecID']) { + case 'V_MS/VFW/FOURCC': + if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, false)) { + $this->getid3->warning('Unable to parse codec private data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio-video.riff.php"'); + break; + } + $parsed = getid3_riff::ParseBITMAPINFOHEADER($trackarray['CodecPrivate']); + $track_info['codec'] = getid3_riff::RIFFfourccLookup($parsed['fourcc']); + $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $parsed; + break; + } + + $info['video']['streams'][] = $track_info; + break; + + case 2: // Audio + $track_info['sample_rate'] = (isset($trackarray['SamplingFrequency']) ? $trackarray['SamplingFrequency'] : 8000.0); + $track_info['channels'] = (isset($trackarray['Channels']) ? $trackarray['Channels'] : 1); + $track_info['language'] = (isset($trackarray['Language']) ? $trackarray['Language'] : 'eng'); + if (isset($trackarray['BitDepth'])) { $track_info['bits_per_sample'] = $trackarray['BitDepth']; } + //if (isset($trackarray['CodecName'])) { $track_info['codec'] = $trackarray['CodecName']; } + + switch ($trackarray['CodecID']) { + case 'A_PCM/INT/LIT': + case 'A_PCM/INT/BIG': + $track_info['bitrate'] = $trackarray['SamplingFrequency'] * $trackarray['Channels'] * $trackarray['BitDepth']; + break; + + case 'A_AC3': + case 'A_DTS': + case 'A_MPEG/L3': + //case 'A_FLAC': + if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.'.$track_info['dataformat'].'.php', __FILE__, false)) { + $this->getid3->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio.'.$track_info['dataformat'].'.php"'); + break; + } + + if (!isset($info['matroska']['track_data_offsets'][$trackarray['TrackNumber']])) { + $this->getid3->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because $info[matroska][track_data_offsets]['.$trackarray['TrackNumber'].'] not set'); + break; + } + + // create temp instance + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset']; + if ($track_info['dataformat'] == 'mp3' || $track_info['dataformat'] == 'flac') { + $getid3_temp->info['avdataend'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset'] + $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['length']; + } + + // analyze + $class = 'getid3_'.$track_info['dataformat']; + $header_data_key = $track_info['dataformat'] == 'mp3' ? 'mpeg' : $track_info['dataformat']; + $getid3_audio = new $class($getid3_temp); + if ($track_info['dataformat'] == 'mp3') { + $getid3_audio->allow_bruteforce = true; + } + if ($track_info['dataformat'] == 'flac') { + $getid3_audio->AnalyzeString($trackarray['CodecPrivate']); + } + else { + $getid3_audio->Analyze(); + } + if (!empty($getid3_temp->info[$header_data_key])) { + unset($getid3_temp->info[$header_data_key]['GETID3_VERSION']); + $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info[$header_data_key]; + if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { + foreach ($getid3_temp->info['audio'] as $key => $value) { + $track_info[$key] = $value; + } + } + } + else { + $this->getid3->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because '.$class.'::Analyze() failed at offset '.$getid3_temp->info['avdataoffset']); + } + + // copy errors and warnings + if (!empty($getid3_temp->info['error'])) { + foreach ($getid3_temp->info['error'] as $newerror) { + $this->getid3->warning($class.'() says: ['.$newerror.']'); + } + } + if (!empty($getid3_temp->info['warning'])) { + foreach ($getid3_temp->info['warning'] as $newerror) { + if ($track_info['dataformat'] == 'mp3' && preg_match('/^Probable truncated file: expecting \d+ bytes of audio data, only found \d+ \(short by \d+ bytes\)$/', $newerror)) { + // LAME/Xing header is probably set, but audio data is chunked into Matroska file and near-impossible to verify if audio stream is complete, so ignore useless warning + continue; + } + $this->getid3->warning($class.'() says: ['.$newerror.']'); + } + } + unset($getid3_temp, $getid3_audio); + break; + + case 'A_AAC': + case 'A_AAC/MPEG2/LC': + case 'A_AAC/MPEG4/LC': + case 'A_AAC/MPEG4/LC/SBR': + $this->getid3->warning($trackarray['CodecID'].' audio data contains no header, audio/video bitrates can\'t be calculated'); + break; + + case 'A_VORBIS': + if (!isset($trackarray['CodecPrivate'])) { + $this->getid3->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because CodecPrivate data not set'); + break; + } + $vorbis_offset = strpos($trackarray['CodecPrivate'], 'vorbis', 1); + if ($vorbis_offset === false) { + $this->getid3->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because CodecPrivate data does not contain "vorbis" keyword'); + break; + } + $vorbis_offset -= 1; + + if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, false)) { + $this->getid3->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio.ogg.php"'); + } + + // create temp instance + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + + // analyze + $getid3_ogg = new getid3_ogg($getid3_temp); + $oggpageinfo['page_seqno'] = 0; + $getid3_ogg->ParseVorbisPageHeader($trackarray['CodecPrivate'], $vorbis_offset, $oggpageinfo); + if (!empty($getid3_temp->info['ogg'])) { + $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info['ogg']; + if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { + foreach ($getid3_temp->info['audio'] as $key => $value) { + $track_info[$key] = $value; + } + } + } + + // copy errors and warnings + if (!empty($getid3_temp->info['error'])) { + foreach ($getid3_temp->info['error'] as $newerror) { + $this->getid3->warning('getid3_ogg() says: ['.$newerror.']'); + } + } + if (!empty($getid3_temp->info['warning'])) { + foreach ($getid3_temp->info['warning'] as $newerror) { + $this->getid3->warning('getid3_ogg() says: ['.$newerror.']'); + } + } + + if (!empty($getid3_temp->info['ogg']['bitrate_nominal'])) { + $track_info['bitrate'] = $getid3_temp->info['ogg']['bitrate_nominal']; + } + unset($getid3_temp, $getid3_ogg, $oggpageinfo, $vorbis_offset); + break; + + case 'A_MS/ACM': + if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, false)) { + $this->getid3->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio-video.riff.php"'); + break; + } + + $parsed = getid3_riff::RIFFparseWAVEFORMATex($trackarray['CodecPrivate']); + foreach ($parsed as $key => $value) { + if ($key != 'raw') { + $track_info[$key] = $value; + } + } + $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $parsed; + break; + + default: + $this->getid3->warning('Unhandled audio type "'.(isset($trackarray['CodecID']) ? $trackarray['CodecID'] : '').'"'); + } + + $info['audio']['streams'][] = $track_info; + break; + } + } + + if (!empty($info['video']['streams'])) { + $info['video'] = self::getDefaultStreamInfo($info['video']['streams']); + } + if (!empty($info['audio']['streams'])) { + $info['audio'] = self::getDefaultStreamInfo($info['audio']['streams']); + } + } + + // determine mime type + if (!empty($info['video']['streams'])) { + $info['mime_type'] = ($info['matroska']['doctype'] == 'webm' ? 'video/webm' : 'video/x-matroska'); + } elseif (!empty($info['audio']['streams'])) { + $info['mime_type'] = ($info['matroska']['doctype'] == 'webm' ? 'audio/webm' : 'audio/x-matroska'); + } elseif (isset($info['mime_type'])) { + unset($info['mime_type']); + } + + return true; + } + + +/////////////////////////////////////// + + private function parseEBML(&$info) + { + // http://www.matroska.org/technical/specs/index.html#EBMLBasics + $this->current_offset = $info['avdataoffset']; + + while ($this->getEBMLelement($top_element, $info['avdataend'])) { + switch ($top_element['id']) { + case EBML_ID_EBML: - $ThisFileInfo['fileformat'] = 'matroska'; - $ThisFileInfo['matroska']['header']['offset'] = $top_element_offset; - $ThisFileInfo['matroska']['header']['length'] = $top_element_length; - - while ($offset < $top_element_endoffset) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $element_data = array(); - $element_data_offset = $offset; - $element_data['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $element_data['id_name'] = $this->EBMLidName($element_data['id']); - $element_data['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $end_offset = $offset + $element_data['length']; + $info['fileformat'] = 'matroska'; + $info['matroska']['header']['offset'] = $top_element['offset']; + $info['matroska']['header']['length'] = $top_element['length']; + while ($this->getEBMLelement($element_data, $top_element['end'], true)) { switch ($element_data['id']) { + case EBML_ID_EBMLVERSION: case EBML_ID_EBMLREADVERSION: case EBML_ID_EBMLMAXIDLENGTH: case EBML_ID_EBMLMAXSIZELENGTH: case EBML_ID_DOCTYPEVERSION: case EBML_ID_DOCTYPEREADVERSION: - $element_data['data'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $element_data['length'])); + $element_data['data'] = getid3_lib::BigEndian2Int($element_data['data']); break; + case EBML_ID_DOCTYPE: - $element_data['data'] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $element_data['length']), "\x00"); + $element_data['data'] = getid3_lib::trimNullByte($element_data['data']); + $info['matroska']['doctype'] = $element_data['data']; break; + + case EBML_ID_CRC32: // not useful, ignore + $this->current_offset = $element_data['end']; + unset($element_data); + break; + default: - $this->warnings[] = 'Unhandled track.video element['.__LINE__.'] ('.$element_data['id'].'::'.$element_data['id_name'].') at '.$element_data_offset; - break; + $this->unhandledElement('header', __LINE__, $element_data); + } + if (!empty($element_data)) { + unset($element_data['offset'], $element_data['end']); + $info['matroska']['header']['elements'][] = $element_data; } - $offset = $end_offset; - $ThisFileInfo['matroska']['header']['elements'][] = $element_data; } break; - case EBML_ID_SEGMENT: - $ThisFileInfo['matroska']['segment'][0]['offset'] = $top_element_offset; - $ThisFileInfo['matroska']['segment'][0]['length'] = $top_element_length; + $info['matroska']['segment'][0]['offset'] = $top_element['offset']; + $info['matroska']['segment'][0]['length'] = $top_element['length']; - $segment_key = -1; - while ($offset < $ThisFileInfo['avdataend']) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - - $element_data = array(); - $element_data['offset'] = $offset; - $element_data['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $element_data['id_name'] = $this->EBMLidName($element_data['id']); - $element_data['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - if ($element_data['length'] === false) { - $this->warnings[] = 'invalid chunk length at '.$element_data['offset']; - //$offset = pow(2, 63); - $offset = $ThisFileInfo['avdataend']; - break; + while ($this->getEBMLelement($element_data, $top_element['end'])) { + if ($element_data['id'] != EBML_ID_CLUSTER || !self::$hide_clusters) { // collect clusters only if required + $info['matroska']['segments'][] = $element_data; } - $element_end = $offset + $element_data['length']; switch ($element_data['id']) { - //case EBML_ID_CLUSTER: - // // too many cluster entries, probably not useful - // break; - case false: - $this->warnings[] = 'invalid ID at '.$element_data['offset']; - $offset = $element_end; - continue 3; - default: - $ThisFileInfo['matroska']['segments'][] = $element_data; - break; - } - $segment_key++; - switch ($element_data['id']) { - case EBML_ID_SEEKHEAD: // Contains the position of other level 1 elements - while ($offset < $element_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $seek_entry = array(); - $seek_entry['offset'] = $offset; - $seek_entry['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $seek_entry['id_name'] = $this->EBMLidName($seek_entry['id']); - $seek_entry['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $seek_end_offset = $offset + $seek_entry['length']; + case EBML_ID_SEEKHEAD: // Contains the position of other level 1 elements. + + while ($this->getEBMLelement($seek_entry, $element_data['end'])) { switch ($seek_entry['id']) { + case EBML_ID_SEEK: // Contains a single seek entry to an EBML element - while ($offset < $seek_end_offset) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $value = substr($EBMLdata, $offset - $EBMLdata_offset, $length); - $offset += $length; - switch ($id) { + while ($this->getEBMLelement($sub_seek_entry, $seek_entry['end'], true)) { + + switch ($sub_seek_entry['id']) { + case EBML_ID_SEEKID: - $dummy = 0; - $seek_entry['target_id'] = $this->readEBMLint($value, $dummy); - $seek_entry['target_name'] = $this->EBMLidName($seek_entry['target_id']); + $seek_entry['target_id'] = self::EBML2Int($sub_seek_entry['data']); + $seek_entry['target_name'] = self::EBMLidName($seek_entry['target_id']); break; + case EBML_ID_SEEKPOSITION: - $seek_entry['target_offset'] = $element_data['offset'] + getid3_lib::BigEndian2Int($value); + $seek_entry['target_offset'] = $element_data['offset'] + getid3_lib::BigEndian2Int($sub_seek_entry['data']); break; + default: - $ThisFileInfo['error'][] = 'Unhandled segment['.__LINE__.'] ('.$id.') at '.$offset; - break; - } + $this->unhandledElement('seekhead.seek', __LINE__, $sub_seek_entry); } + } + + if ($seek_entry['target_id'] != EBML_ID_CLUSTER || !self::$hide_clusters) { // collect clusters only if required + $info['matroska']['seek'][] = $seek_entry; } - $ThisFileInfo['matroska']['seek'][] = $seek_entry; - //switch ($seek_entry['target_id']) { - // case EBML_ID_CLUSTER: - // // too many cluster seek points, probably not useful - // break; - // default: - // $ThisFileInfo['matroska']['seek'][] = $seek_entry; - // break; - //} break; + default: - $this->warnings[] = 'Unhandled seekhead element['.__LINE__.'] ('.$seek_entry['id'].') at '.$offset; - break; + $this->unhandledElement('seekhead', __LINE__, $seek_entry); } - $offset = $seek_end_offset; } break; - case EBML_ID_TRACKS: // information about all tracks in segment - $ThisFileInfo['matroska']['tracks'] = $element_data; - while ($offset < $element_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $track_entry = array(); - $track_entry['offset'] = $offset; - $track_entry['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $track_entry['id_name'] = $this->EBMLidName($track_entry['id']); - $track_entry['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $track_entry_endoffset = $offset + $track_entry['length']; + case EBML_ID_TRACKS: // A top-level block of information with many tracks described. + $info['matroska']['tracks'] = $element_data; + + while ($this->getEBMLelement($track_entry, $element_data['end'])) { switch ($track_entry['id']) { + case EBML_ID_TRACKENTRY: //subelements: Describes a track with all elements. - while ($offset < $track_entry_endoffset) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $subelement_offset = $offset; - $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $subelement_idname = $this->EBMLidName($subelement_id); - $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $subelement_end = $offset + $subelement_length; - switch ($subelement_id) { + + while ($this->getEBMLelement($subelement, $track_entry['end'], array(EBML_ID_VIDEO, EBML_ID_AUDIO, EBML_ID_CONTENTENCODINGS))) { + switch ($subelement['id']) { + case EBML_ID_TRACKNUMBER: case EBML_ID_TRACKUID: case EBML_ID_TRACKTYPE: @@ -382,33 +574,37 @@ class getid3_matroska case EBML_ID_MAXCACHE: case EBML_ID_MAXBLOCKADDITIONID: case EBML_ID_DEFAULTDURATION: // nanoseconds per frame - $track_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); + $track_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']); break; + case EBML_ID_TRACKTIMECODESCALE: - $track_entry[$subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); + $track_entry[$subelement['id_name']] = getid3_lib::BigEndian2Float($subelement['data']); break; + case EBML_ID_CODECID: case EBML_ID_LANGUAGE: case EBML_ID_NAME: - case EBML_ID_CODECPRIVATE: - $track_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), "\x00"); + case EBML_ID_CODECNAME: + $track_entry[$subelement['id_name']] = getid3_lib::trimNullByte($subelement['data']); break; + + case EBML_ID_CODECPRIVATE: + $track_entry[$subelement['id_name']] = $subelement['data']; + break; + case EBML_ID_FLAGENABLED: case EBML_ID_FLAGDEFAULT: case EBML_ID_FLAGFORCED: case EBML_ID_FLAGLACING: case EBML_ID_CODECDECODEALL: - $track_entry[$subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); + $track_entry[$subelement['id_name']] = (bool) getid3_lib::BigEndian2Int($subelement['data']); break; + case EBML_ID_VIDEO: - while ($offset < $subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_offset = $offset; - $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); - $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_end = $offset + $sub_subelement_length; - switch ($sub_subelement_id) { + + while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) { + switch ($sub_subelement['id']) { + case EBML_ID_PIXELWIDTH: case EBML_ID_PIXELHEIGHT: case EBML_ID_STEREOMODE: @@ -420,978 +616,581 @@ class getid3_matroska case EBML_ID_DISPLAYHEIGHT: case EBML_ID_DISPLAYUNIT: case EBML_ID_ASPECTRATIOTYPE: - $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); + $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); break; - case EBML_ID_FLAGINTERLACED: - $track_entry[$sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); - break; - case EBML_ID_GAMMAVALUE: - $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); - break; - case EBML_ID_COLOURSPACE: - $track_entry[$sub_subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length), "\x00"); - break; - default: - $this->warnings[] = 'Unhandled track.video element['.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; - break; - } - $offset = $sub_subelement_end; - } - if ((@$track_entry[$this->EBMLidName(EBML_ID_CODECID)] == 'V_MS/VFW/FOURCC') && isset($track_entry[$this->EBMLidName(EBML_ID_CODECPRIVATE)])) { - if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, false)) { - $track_entry['codec_private_parsed'] = getid3_riff::ParseBITMAPINFOHEADER($track_entry[$this->EBMLidName(EBML_ID_CODECPRIVATE)]); - } else { - $this->warnings[] = 'Unable to parse codec private data['.__LINE__.'] because cannot include "module.audio-video.riff.php"'; + case EBML_ID_FLAGINTERLACED: + $track_entry[$sub_subelement['id_name']] = (bool)getid3_lib::BigEndian2Int($sub_subelement['data']); + break; + + case EBML_ID_GAMMAVALUE: + $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Float($sub_subelement['data']); + break; + + case EBML_ID_COLOURSPACE: + $track_entry[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']); + break; + + default: + $this->unhandledElement('track.video', __LINE__, $sub_subelement); } } break; + case EBML_ID_AUDIO: - while ($offset < $subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_offset = $offset; - $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); - $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_end = $offset + $sub_subelement_length; - switch ($sub_subelement_id) { + + while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) { + switch ($sub_subelement['id']) { + case EBML_ID_CHANNELS: case EBML_ID_BITDEPTH: - $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); + $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); break; + case EBML_ID_SAMPLINGFREQUENCY: case EBML_ID_OUTPUTSAMPLINGFREQUENCY: - $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); + $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Float($sub_subelement['data']); break; + case EBML_ID_CHANNELPOSITIONS: - $track_entry[$sub_subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length), "\x00"); + $track_entry[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']); break; + default: - $this->warnings[] = 'Unhandled track.video element['.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; - break; + $this->unhandledElement('track.audio', __LINE__, $sub_subelement); } - $offset = $sub_subelement_end; } break; case EBML_ID_CONTENTENCODINGS: - while ($offset < $subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_offset = $offset; - $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); - $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_end = $offset + $sub_subelement_length; - switch ($sub_subelement_id) { + + while ($this->getEBMLelement($sub_subelement, $subelement['end'])) { + switch ($sub_subelement['id']) { + case EBML_ID_CONTENTENCODING: - while ($offset < $sub_subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_offset = $offset; - $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); - $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; - switch ($sub_sub_subelement_id) { + + while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], array(EBML_ID_CONTENTCOMPRESSION, EBML_ID_CONTENTENCRYPTION))) { + switch ($sub_sub_subelement['id']) { + case EBML_ID_CONTENTENCODINGORDER: case EBML_ID_CONTENTENCODINGSCOPE: case EBML_ID_CONTENTENCODINGTYPE: - $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); + $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); break; + case EBML_ID_CONTENTCOMPRESSION: - while ($offset < $sub_sub_subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_sub_subelement_offset = $offset; - $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); - $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; - switch ($sub_sub_sub_subelement_id) { + + while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { + switch ($sub_sub_sub_subelement['id']) { + case EBML_ID_CONTENTCOMPALGO: - $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length)); + $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_sub_subelement['data']); break; + case EBML_ID_CONTENTCOMPSETTINGS: - $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length); + $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = $sub_sub_sub_subelement['data']; break; + default: - $this->warnings[] = 'Unhandled track.contentencodings.contentencoding.contentcompression element['.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; - break; + $this->unhandledElement('track.contentencodings.contentencoding.contentcompression', __LINE__, $sub_sub_sub_subelement); } - $offset = $sub_sub_sub_subelement_end; } break; case EBML_ID_CONTENTENCRYPTION: - while ($offset < $sub_sub_subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_sub_subelement_offset = $offset; - $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); - $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; - switch ($sub_sub_sub_subelement_id) { + + while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { + switch ($sub_sub_sub_subelement['id']) { + case EBML_ID_CONTENTENCALGO: case EBML_ID_CONTENTSIGALGO: case EBML_ID_CONTENTSIGHASHALGO: - $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length)); + $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_sub_subelement['data']); break; + case EBML_ID_CONTENTENCKEYID: case EBML_ID_CONTENTSIGNATURE: case EBML_ID_CONTENTSIGKEYID: - $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length); + $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = $sub_sub_sub_subelement['data']; break; + default: - $this->warnings[] = 'Unhandled track.contentencodings.contentencoding.contentcompression element['.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; - break; + $this->unhandledElement('track.contentencodings.contentencoding.contentcompression', __LINE__, $sub_sub_sub_subelement); } - $offset = $sub_sub_sub_subelement_end; } break; default: - $this->warnings[] = 'Unhandled track.contentencodings.contentencoding element['.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; - break; + $this->unhandledElement('track.contentencodings.contentencoding', __LINE__, $sub_sub_subelement); } - $offset = $sub_sub_subelement_end; } break; + default: - $this->warnings[] = 'Unhandled track.contentencodings element['.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; - break; + $this->unhandledElement('track.contentencodings', __LINE__, $sub_subelement); } - $offset = $sub_subelement_end; } break; default: - $this->warnings[] = 'Unhandled track element['.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; - break; + $this->unhandledElement('track', __LINE__, $subelement); } - $offset = $subelement_end; } + + $info['matroska']['tracks']['tracks'][] = $track_entry; break; + default: - $this->warnings[] = 'Unhandled track element['.__LINE__.'] ('.$track_entry['id'].'::'.$track_entry['id_name'].') at '.$track_entry['offset']; - $offset = $track_entry_endoffset; - break; + $this->unhandledElement('tracks', __LINE__, $track_entry); } - $ThisFileInfo['matroska']['tracks']['tracks'][] = $track_entry; } break; - case EBML_ID_INFO: // Contains the position of other level 1 elements + case EBML_ID_INFO: // Contains miscellaneous general information and statistics on the file. $info_entry = array(); - while ($offset < $element_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $subelement_offset = $offset; - $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $subelement_idname = $this->EBMLidName($subelement_id); - $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $subelement_end = $offset + $subelement_length; - switch ($subelement_id) { + + while ($this->getEBMLelement($subelement, $element_data['end'], true)) { + switch ($subelement['id']) { + case EBML_ID_CHAPTERTRANSLATEEDITIONUID: case EBML_ID_CHAPTERTRANSLATECODEC: case EBML_ID_TIMECODESCALE: - $info_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); + $info_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']); break; + case EBML_ID_DURATION: - $info_entry[$subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); + $info_entry[$subelement['id_name']] = getid3_lib::BigEndian2Float($subelement['data']); break; + case EBML_ID_DATEUTC: - $info_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); - $info_entry[$subelement_idname.'_unix'] = $this->EBMLdate2unix($info_entry[$subelement_idname]); + $info_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']); + $info_entry[$subelement['id_name'].'_unix'] = self::EBMLdate2unix($info_entry[$subelement['id_name']]); break; + case EBML_ID_SEGMENTUID: case EBML_ID_PREVUID: case EBML_ID_NEXTUID: case EBML_ID_SEGMENTFAMILY: case EBML_ID_CHAPTERTRANSLATEID: - $info_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), "\x00"); + $info_entry[$subelement['id_name']] = getid3_lib::trimNullByte($subelement['data']); break; + case EBML_ID_SEGMENTFILENAME: case EBML_ID_PREVFILENAME: case EBML_ID_NEXTFILENAME: case EBML_ID_TITLE: case EBML_ID_MUXINGAPP: case EBML_ID_WRITINGAPP: - $info_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), "\x00"); + $info_entry[$subelement['id_name']] = getid3_lib::trimNullByte($subelement['data']); + $info['matroska']['comments'][strtolower($subelement['id_name'])][] = $info_entry[$subelement['id_name']]; break; + default: - $this->warnings[] = 'Unhandled info element['.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; - break; + $this->unhandledElement('info', __LINE__, $subelement); } - $offset = $subelement_end; } - $ThisFileInfo['matroska']['info'][] = $info_entry; + $info['matroska']['info'][] = $info_entry; break; - case EBML_ID_CUES: + case EBML_ID_CUES: // A top-level element to speed seeking access. All entries are local to the segment. Should be mandatory for non "live" streams. + if (self::$hide_clusters) { // do not parse cues if hide clusters is "ON" till they point to clusters anyway + $this->current_offset = $element_data['end']; + break; + } $cues_entry = array(); - while ($offset < $element_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $subelement_offset = $offset; - $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $subelement_idname = $this->EBMLidName($subelement_id); - $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $subelement_end = $offset + $subelement_length; - switch ($subelement_id) { + + while ($this->getEBMLelement($subelement, $element_data['end'])) { + switch ($subelement['id']) { + case EBML_ID_CUEPOINT: $cuepoint_entry = array(); - while ($offset < $subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_offset = $offset; - $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); - $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_end = $offset + $sub_subelement_length; - switch ($sub_subelement_id) { + + while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_CUETRACKPOSITIONS))) { + switch ($sub_subelement['id']) { + case EBML_ID_CUETRACKPOSITIONS: - while ($offset < $sub_subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_offset = $offset; - $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); - $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; - switch ($sub_sub_subelement_id) { + $cuetrackpositions_entry = array(); + + while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], true)) { + switch ($sub_sub_subelement['id']) { + case EBML_ID_CUETRACK: - $cuepoint_entry[$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); + case EBML_ID_CUECLUSTERPOSITION: + case EBML_ID_CUEBLOCKNUMBER: + case EBML_ID_CUECODECSTATE: + $cuetrackpositions_entry[$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); break; + default: - $this->warnings[] = 'Unhandled cues.cuepoint.cuetrackpositions element['.__LINE__.'] ('.$sub_sub_subelement_id.'::'.$sub_sub_subelement_idname.') at '.$sub_sub_subelement_offset; - break; + $this->unhandledElement('cues.cuepoint.cuetrackpositions', __LINE__, $sub_sub_subelement); } - $offset = $sub_subelement_end; } + $cuepoint_entry[$sub_subelement['id_name']][] = $cuetrackpositions_entry; break; + case EBML_ID_CUETIME: - $cuepoint_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); + $cuepoint_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); break; + default: - $this->warnings[] = 'Unhandled cues.cuepoint element['.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; - break; + $this->unhandledElement('cues.cuepoint', __LINE__, $sub_subelement); } - $offset = $sub_subelement_end; } $cues_entry[] = $cuepoint_entry; - $offset = $sub_subelement_end; break; + default: - $this->warnings[] = 'Unhandled cues element['.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; - break; + $this->unhandledElement('cues', __LINE__, $subelement); } - $offset = $subelement_end; } - $ThisFileInfo['matroska']['cues'] = $cues_entry; + $info['matroska']['cues'] = $cues_entry; break; - case EBML_ID_TAGS: - $tags_entry = array(); - while ($offset < $element_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $subelement_offset = $offset; - $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $subelement_idname = $this->EBMLidName($subelement_id); - $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $subelement_end = $offset + $subelement_length; - switch ($subelement_id) { - case EBML_ID_WRITINGAPP: - $tags_entry[$subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length); - break; + case EBML_ID_TAGS: // Element containing elements specific to Tracks/Chapters. + $tags_entry = array(); + + while ($this->getEBMLelement($subelement, $element_data['end'], false)) { + switch ($subelement['id']) { + case EBML_ID_TAG: $tag_entry = array(); - while ($offset < $subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_offset = $offset; - $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); - $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_end = $offset + $sub_subelement_length; - switch ($sub_subelement_id) { + + while ($this->getEBMLelement($sub_subelement, $subelement['end'], false)) { + switch ($sub_subelement['id']) { + case EBML_ID_TARGETS: $targets_entry = array(); - while ($offset < $sub_subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_offset = $offset; - $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); - $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; - switch ($sub_sub_subelement_id) { + + while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], true)) { + switch ($sub_sub_subelement['id']) { + case EBML_ID_TARGETTYPEVALUE: - case EBML_ID_EDITIONUID: - case EBML_ID_CHAPTERUID: - case EBML_ID_ATTACHMENTUID: - case EBML_ID_TAGTRACKUID: + $targets_entry[$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); + $targets_entry[strtolower($sub_sub_subelement['id_name']).'_long'] = self::MatroskaTargetTypeValue($targets_entry[$sub_sub_subelement['id_name']]); + break; + + case EBML_ID_TARGETTYPE: + $targets_entry[$sub_sub_subelement['id_name']] = $sub_sub_subelement['data']; + break; + + case EBML_ID_TAGTRACKUID: + case EBML_ID_TAGEDITIONUID: case EBML_ID_TAGCHAPTERUID: - $targets_entry[$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); + case EBML_ID_TAGATTACHMENTUID: + $targets_entry[$sub_sub_subelement['id_name']][] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); break; + default: - $this->warnings[] = 'Unhandled tag.targets element['.__LINE__.'] ('.$sub_sub_subelement_id.'::'.$sub_sub_subelement_idname.') at '.$sub_sub_subelement_offset; - break; + $this->unhandledElement('tags.tag.targets', __LINE__, $sub_sub_subelement); } - $offset = $sub_sub_subelement_end; } - $tag_entry[$sub_subelement_idname][] = $targets_entry; + $tag_entry[$sub_subelement['id_name']] = $targets_entry; break; + case EBML_ID_SIMPLETAG: - $simpletag_entry = array(); - while ($offset < $sub_subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_offset = $offset; - $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); - $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; - switch ($sub_sub_subelement_id) { - case EBML_ID_TAGNAME: - case EBML_ID_TAGLANGUAGE: - case EBML_ID_TAGSTRING: - case EBML_ID_TAGBINARY: - $simpletag_entry[$sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length); - break; - case EBML_ID_TAGDEFAULT: - $simpletag_entry[$sub_sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); - break; - default: - $this->warnings[] = 'Unhandled tag.simpletag element['.__LINE__.'] ('.$sub_sub_subelement_id.'::'.$sub_sub_subelement_idname.') at '.$sub_sub_subelement_offset; - break; - } - $offset = $sub_sub_subelement_end; - } - $tag_entry[$sub_subelement_idname][] = $simpletag_entry; - break; - case EBML_ID_TARGETTYPE: - $tag_entry[$sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length); - break; - case EBML_ID_TRACKUID: - $tag_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); + $tag_entry[$sub_subelement['id_name']][] = $this->HandleEMBLSimpleTag($sub_subelement['end']); break; + default: - $this->warnings[] = 'Unhandled tags.tag element['.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; - break; + $this->unhandledElement('tags.tag', __LINE__, $sub_subelement); } - $offset = $sub_subelement_end; } - $tags_entry['tags'][] = $tag_entry; - $offset = $sub_subelement_end; + $tags_entry[] = $tag_entry; break; + default: - $this->warnings[] = 'Unhandled tags element['.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; - break; + $this->unhandledElement('tags', __LINE__, $subelement); } - $offset = $subelement_end; } - $ThisFileInfo['matroska']['tags'] = $tags_entry; + $info['matroska']['tags'] = $tags_entry; break; + case EBML_ID_ATTACHMENTS: // Contain attached files. + + while ($this->getEBMLelement($subelement, $element_data['end'])) { + switch ($subelement['id']) { - case EBML_ID_ATTACHMENTS: - while ($offset < $element_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $subelement_offset = $offset; - $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $subelement_idname = $this->EBMLidName($subelement_id); - $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $subelement_end = $offset + $subelement_length; - switch ($subelement_id) { case EBML_ID_ATTACHEDFILE: $attachedfile_entry = array(); - while ($offset < $subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_offset = $offset; - $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); - $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_end = $offset + $sub_subelement_length; - switch ($sub_subelement_id) { + + while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_FILEDATA))) { + switch ($sub_subelement['id']) { + case EBML_ID_FILEDESCRIPTION: case EBML_ID_FILENAME: case EBML_ID_FILEMIMETYPE: - $attachedfile_entry[$sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length); + $attachedfile_entry[$sub_subelement['id_name']] = $sub_subelement['data']; break; case EBML_ID_FILEDATA: - $attachedfile_entry['data_offset'] = $offset; - $attachedfile_entry['data_length'] = $sub_subelement_length; - if ($sub_subelement_length < 1024) { - $attachedfile_entry[$sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length); + $attachedfile_entry['data_offset'] = $this->current_offset; + $attachedfile_entry['data_length'] = $sub_subelement['length']; + + $this->getid3->saveAttachment( + $attachedfile_entry[$sub_subelement['id_name']], + $attachedfile_entry['FileName'], + $attachedfile_entry['data_offset'], + $attachedfile_entry['data_length']); + + if (@$attachedfile_entry[$sub_subelement['id_name']] && is_file($attachedfile_entry[$sub_subelement['id_name']])) { + $attachedfile_entry[$sub_subelement['id_name'].'_filename'] = $attachedfile_entry[$sub_subelement['id_name']]; + unset($attachedfile_entry[$sub_subelement['id_name']]); } + + $this->current_offset = $sub_subelement['end']; break; case EBML_ID_FILEUID: - $attachedfile_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); + $attachedfile_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); break; default: - $this->warnings[] = 'Unhandled attachment.attachedfile element['.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; - break; + $this->unhandledElement('attachments.attachedfile', __LINE__, $sub_subelement); } - $offset = $sub_subelement_end; } - $ThisFileInfo['matroska']['attachments'][] = $attachedfile_entry; - $offset = $sub_subelement_end; + if (!empty($attachedfile_entry['FileData']) && !empty($attachedfile_entry['FileMimeType']) && preg_match('#^image/#i', $attachedfile_entry['FileMimeType'])) { + if ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_INLINE) { + $attachedfile_entry['data'] = $attachedfile_entry['FileData']; + $attachedfile_entry['image_mime'] = $attachedfile_entry['FileMimeType']; + $info['matroska']['comments']['picture'][] = array('data' => $attachedfile_entry['data'], 'image_mime' => $attachedfile_entry['image_mime'], 'filename' => (!empty($attachedfile_entry['FileName']) ? $attachedfile_entry['FileName'] : '')); + unset($attachedfile_entry['FileData'], $attachedfile_entry['FileMimeType']); + } + } + if (!empty($attachedfile_entry['image_mime']) && preg_match('#^image/#i', $attachedfile_entry['image_mime'])) { + // don't add a second copy of attached images, which are grouped under the standard location [comments][picture] + } else { + $info['matroska']['attachments'][] = $attachedfile_entry; + } break; + default: - $this->warnings[] = 'Unhandled tags element['.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; - break; + $this->unhandledElement('attachments', __LINE__, $subelement); } - $offset = $subelement_end; } break; + case EBML_ID_CHAPTERS: + + while ($this->getEBMLelement($subelement, $element_data['end'])) { + switch ($subelement['id']) { - case EBML_ID_CHAPTERS: // not important to us, contains mostly actual audio/video data, ignore - while ($offset < $element_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $subelement_offset = $offset; - $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $subelement_idname = $this->EBMLidName($subelement_id); - $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $subelement_end = $offset + $subelement_length; - switch ($subelement_id) { case EBML_ID_EDITIONENTRY: $editionentry_entry = array(); - while ($offset < $subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_offset = $offset; - $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); - $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_end = $offset + $sub_subelement_length; - switch ($sub_subelement_id) { + + while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_CHAPTERATOM))) { + switch ($sub_subelement['id']) { + case EBML_ID_EDITIONUID: - $editionentry_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); + $editionentry_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); break; + case EBML_ID_EDITIONFLAGHIDDEN: case EBML_ID_EDITIONFLAGDEFAULT: case EBML_ID_EDITIONFLAGORDERED: - $editionentry_entry[$sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); + $editionentry_entry[$sub_subelement['id_name']] = (bool)getid3_lib::BigEndian2Int($sub_subelement['data']); break; + case EBML_ID_CHAPTERATOM: $chapteratom_entry = array(); - while ($offset < $sub_subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_offset = $offset; - $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); - $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; - switch ($sub_sub_subelement_id) { + + while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], array(EBML_ID_CHAPTERTRACK, EBML_ID_CHAPTERDISPLAY))) { + switch ($sub_sub_subelement['id']) { + case EBML_ID_CHAPTERSEGMENTUID: case EBML_ID_CHAPTERSEGMENTEDITIONUID: - $chapteratom_entry[$sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length); + $chapteratom_entry[$sub_sub_subelement['id_name']] = $sub_sub_subelement['data']; break; + case EBML_ID_CHAPTERFLAGENABLED: case EBML_ID_CHAPTERFLAGHIDDEN: - $chapteratom_entry[$sub_sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); + $chapteratom_entry[$sub_sub_subelement['id_name']] = (bool)getid3_lib::BigEndian2Int($sub_sub_subelement['data']); break; + case EBML_ID_CHAPTERUID: case EBML_ID_CHAPTERTIMESTART: case EBML_ID_CHAPTERTIMEEND: - $chapteratom_entry[$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); + $chapteratom_entry[$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); break; + case EBML_ID_CHAPTERTRACK: $chaptertrack_entry = array(); - while ($offset < $sub_sub_subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_sub_subelement_offset = $offset; - $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); - $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; - switch ($sub_sub_sub_subelement_id) { + + while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { + switch ($sub_sub_sub_subelement['id']) { + case EBML_ID_CHAPTERTRACKNUMBER: - $chaptertrack_entry[$sub_sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length)); + $chaptertrack_entry[$sub_sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_sub_subelement['data']); break; + default: - $this->warnings[] = 'Unhandled chapters.editionentry.chapteratom.chaptertrack element['.__LINE__.'] ('.$sub_sub_sub_subelement_id.'::'.$sub_sub_sub_subelement_idname.') at '.$sub_sub_sub_subelement_offset; - break; + $this->unhandledElement('chapters.editionentry.chapteratom.chaptertrack', __LINE__, $sub_sub_sub_subelement); } - $offset = $sub_sub_sub_subelement_end; } - $chapteratom_entry[$sub_sub_subelement_idname][] = $chaptertrack_entry; + $chapteratom_entry[$sub_sub_subelement['id_name']][] = $chaptertrack_entry; break; + case EBML_ID_CHAPTERDISPLAY: $chapterdisplay_entry = array(); - while ($offset < $sub_sub_subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_sub_subelement_offset = $offset; - $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_sub_subelement_id); - $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; - switch ($sub_sub_sub_subelement_id) { + + while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { + switch ($sub_sub_sub_subelement['id']) { + case EBML_ID_CHAPSTRING: case EBML_ID_CHAPLANGUAGE: case EBML_ID_CHAPCOUNTRY: - $chapterdisplay_entry[$sub_sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length); + $chapterdisplay_entry[$sub_sub_sub_subelement['id_name']] = $sub_sub_sub_subelement['data']; break; + default: - $this->warnings[] = 'Unhandled chapters.editionentry.chapteratom.chapterdisplay element['.__LINE__.'] ('.$sub_sub_sub_subelement_id.'::'.$sub_sub_sub_subelement_idname.') at '.$sub_sub_sub_subelement_offset; - break; + $this->unhandledElement('chapters.editionentry.chapteratom.chapterdisplay', __LINE__, $sub_sub_sub_subelement); } - $offset = $sub_sub_sub_subelement_end; } - $chapteratom_entry[$sub_sub_subelement_idname][] = $chapterdisplay_entry; + $chapteratom_entry[$sub_sub_subelement['id_name']][] = $chapterdisplay_entry; break; + default: - $this->warnings[] = 'Unhandled chapters.editionentry.chapteratom element['.__LINE__.'] ('.$sub_sub_subelement_id.'::'.$sub_sub_subelement_idname.') at '.$sub_sub_subelement_offset; - break; + $this->unhandledElement('chapters.editionentry.chapteratom', __LINE__, $sub_sub_subelement); } - $offset = $sub_sub_subelement_end; } - $editionentry_entry[$sub_subelement_idname][] = $chapteratom_entry; + $editionentry_entry[$sub_subelement['id_name']][] = $chapteratom_entry; break; + default: - $this->warnings[] = 'Unhandled chapters.editionentry element['.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; - break; + $this->unhandledElement('chapters.editionentry', __LINE__, $sub_subelement); } - $offset = $sub_subelement_end; } - $ThisFileInfo['matroska']['chapters'][] = $editionentry_entry; - $offset = $sub_subelement_end; + $info['matroska']['chapters'][] = $editionentry_entry; break; + default: - $this->warnings[] = 'Unhandled chapters element['.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; - break; + $this->unhandledElement('chapters', __LINE__, $subelement); } - $offset = $subelement_end; } break; - - case EBML_ID_VOID: // padding, ignore - $void_entry = array(); - $void_entry['offset'] = $offset; - $ThisFileInfo['matroska']['void'][] = $void_entry; - break; - - case EBML_ID_CLUSTER: // not important to us, contains mostly actual audio/video data, ignore + case EBML_ID_CLUSTER: // The lower level element containing the (monolithic) Block structure. $cluster_entry = array(); - while ($offset < $element_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $subelement_offset = $offset; -//var_dump($offset); - $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); -//var_dump($subelement_id); -//echo '
'; - $subelement_idname = $this->EBMLidName($subelement_id); - $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); -//var_dump($subelement_length); - $subelement_end = $offset + $subelement_length; -//exit; - switch ($subelement_id) { + + while ($this->getEBMLelement($subelement, $element_data['end'], array(EBML_ID_CLUSTERSILENTTRACKS, EBML_ID_CLUSTERBLOCKGROUP, EBML_ID_CLUSTERSIMPLEBLOCK))) { + switch ($subelement['id']) { + case EBML_ID_CLUSTERTIMECODE: case EBML_ID_CLUSTERPOSITION: case EBML_ID_CLUSTERPREVSIZE: - $cluster_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); + $cluster_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']); break; case EBML_ID_CLUSTERSILENTTRACKS: $cluster_silent_tracks = array(); - while ($offset < $subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_offset = $offset; - $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); - $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_end = $offset + $sub_subelement_length; - switch ($sub_subelement_id) { + + while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) { + switch ($sub_subelement['id']) { + case EBML_ID_CLUSTERSILENTTRACKNUMBER: - $cluster_silent_tracks[] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); + $cluster_silent_tracks[] = getid3_lib::BigEndian2Int($sub_subelement['data']); break; + default: - $this->warnings[] = 'Unhandled clusters.silenttracks element['.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; - break; + $this->unhandledElement('cluster.silenttracks', __LINE__, $sub_subelement); } - $offset = $sub_subelement_end; } - $cluster_entry[$subelement_idname][] = $cluster_silent_tracks; - $offset = $sub_subelement_end; + $cluster_entry[$subelement['id_name']][] = $cluster_silent_tracks; break; case EBML_ID_CLUSTERBLOCKGROUP: - $cluster_block_group = array('offset'=>$offset); - while ($offset < $subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_offset = $offset; - $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); - $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_end = $offset + $sub_subelement_length; - switch ($sub_subelement_id) { + $cluster_block_group = array('offset' => $this->current_offset); + + while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_CLUSTERBLOCK))) { + switch ($sub_subelement['id']) { + case EBML_ID_CLUSTERBLOCK: - $cluster_block_data = array(); - $cluster_block_data['tracknumber'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $cluster_block_data['timecode'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 2)); - $offset += 2; - // unsure whether this is 1 octect or 2 octets? (http://matroska.org/technical/specs/index.html#block_structure) - $cluster_block_data['flags_raw'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); - $offset += 1; - //$cluster_block_data['flags']['reserved1'] = (($cluster_block_data['flags_raw'] & 0xF0) >> 4); - $cluster_block_data['flags']['invisible'] = (bool) (($cluster_block_data['flags_raw'] & 0x08) >> 3); - $cluster_block_data['flags']['lacing'] = (($cluster_block_data['flags_raw'] & 0x06) >> 1); - //$cluster_block_data['flags']['reserved2'] = (($cluster_block_data['flags_raw'] & 0x01) >> 0); - $cluster_block_data['flags']['lacing_type'] = $this->MatroskaBlockLacingType($cluster_block_data['flags']['lacing']); - if ($cluster_block_data['flags']['lacing'] != 0) { - $cluster_block_data['lace_frames'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); // Number of frames in the lace-1 (uint8) - $offset += 1; - if ($cluster_block_data['flags']['lacing'] != 2) { - $cluster_block_data['lace_frames'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); // Lace-coded size of each frame of the lace, except for the last one (multiple uint8). *This is not used with Fixed-size lacing as it is calculated automatically from (total size of lace) / (number of frames in lace). - $offset += 1; - } - } - if (!isset($ThisFileInfo['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']])) { - $ThisFileInfo['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']] = $offset; - } - $cluster_block_group[$sub_subelement_idname] = $cluster_block_data; + $cluster_block_group[$sub_subelement['id_name']] = $this->HandleEMBLClusterBlock($sub_subelement, EBML_ID_CLUSTERBLOCK, $info); break; case EBML_ID_CLUSTERREFERENCEPRIORITY: // unsigned-int case EBML_ID_CLUSTERBLOCKDURATION: // unsigned-int - $cluster_block_group[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); + $cluster_block_group[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); break; case EBML_ID_CLUSTERREFERENCEBLOCK: // signed-int - $cluster_block_group[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length), false, true); + $cluster_block_group[$sub_subelement['id_name']][] = getid3_lib::BigEndian2Int($sub_subelement['data'], false, true); + break; + + case EBML_ID_CLUSTERCODECSTATE: + $cluster_block_group[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']); break; default: - $this->warnings[] = 'Unhandled clusters.blockgroup element['.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; - break; + $this->unhandledElement('clusters.blockgroup', __LINE__, $sub_subelement); } - $offset = $sub_subelement_end; } - $cluster_entry[$subelement_idname][] = $cluster_block_group; - $offset = $sub_subelement_end; + $cluster_entry[$subelement['id_name']][] = $cluster_block_group; + break; + + case EBML_ID_CLUSTERSIMPLEBLOCK: + $cluster_entry[$subelement['id_name']][] = $this->HandleEMBLClusterBlock($subelement, EBML_ID_CLUSTERSIMPLEBLOCK, $info); break; default: - $this->warnings[] = 'Unhandled cluster element['.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; - break; + $this->unhandledElement('cluster', __LINE__, $subelement); } - $offset = $subelement_end; + $this->current_offset = $subelement['end']; + } + if (!self::$hide_clusters) { + $info['matroska']['cluster'][] = $cluster_entry; } - $ThisFileInfo['matroska']['cluster'][] = $cluster_entry; // check to see if all the data we need exists already, if so, break out of the loop - if (isset($ThisFileInfo['matroska']['info']) && is_array($ThisFileInfo['matroska']['info'])) { - if (isset($ThisFileInfo['matroska']['tracks']['tracks']) && is_array($ThisFileInfo['matroska']['tracks']['tracks'])) { - break 2; + if (!self::$parse_whole_file) { + if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) { + if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) { + return; + } } } break; default: - if ($element_data['id_name'] == dechex($element_data['id'])) { - $ThisFileInfo['error'][] = 'Unhandled segment['.__LINE__.'] ('.$element_data['id'].') at '.$element_data_offset; - } else { - $this->warnings[] = 'Unhandled segment['.__LINE__.'] ('.$element_data['id'].'::'.$element_data['id_name'].') at '.$element_data['offset']; - } - break; + $this->unhandledElement('segment', __LINE__, $element_data); } - $offset = $element_end; } break; - default: - $ThisFileInfo['error'][] = 'Unhandled chunk['.__LINE__.'] ('.$top_element_id.') at '.$offset; - break; - } - $offset = $top_element_endoffset; - } - - - - if (isset($ThisFileInfo['matroska']['info']) && is_array($ThisFileInfo['matroska']['info'])) { - foreach ($ThisFileInfo['matroska']['info'] as $key => $infoarray) { - if (isset($infoarray['Duration'])) { - // TimecodeScale is how many nanoseconds each Duration unit is - $ThisFileInfo['playtime_seconds'] = $infoarray['Duration'] * ((isset($infoarray['TimecodeScale']) ? $infoarray['TimecodeScale'] : 1000000) / 1000000000); - break; - } + $this->unhandledElement('root', __LINE__, $top_element); } } - if (isset($ThisFileInfo['matroska']['tracks']['tracks']) && is_array($ThisFileInfo['matroska']['tracks']['tracks'])) { - foreach ($ThisFileInfo['matroska']['tracks']['tracks'] as $key => $trackarray) { - $track_info = array(); - switch (@$trackarray['TrackType']) { - case 1: // Video - if (@$trackarray['PixelWidth']) { $track_info['resolution_x'] = $trackarray['PixelWidth']; } - if (@$trackarray['PixelHeight']) { $track_info['resolution_y'] = $trackarray['PixelHeight']; } - if (@$trackarray['DisplayWidth']) { $track_info['display_x'] = $trackarray['DisplayWidth']; } - if (@$trackarray['DisplayHeight']) { $track_info['display_y'] = $trackarray['DisplayHeight']; } - if (@$trackarray['DefaultDuration']) { $track_info['frame_rate'] = round(1000000000 / $trackarray['DefaultDuration'], 3); } - if (@$trackarray['CodecID']) { $track_info['dataformat'] = $this->MatroskaCodecIDtoCommonName($trackarray['CodecID']); } - if (!empty($trackarray['codec_private_parsed']['fourcc'])) { - $track_info['fourcc'] = $trackarray['codec_private_parsed']['fourcc']; - } - $ThisFileInfo['video']['streams'][] = $track_info; - if (isset($track_info['resolution_x']) && empty($ThisFileInfo['video']['resolution_x'])) { - foreach ($track_info as $key => $value) { - $ThisFileInfo['video'][$key] = $value; - } - } - break; - case 2: // Audio - if (@$trackarray['CodecID']) { $track_info['dataformat'] = $this->MatroskaCodecIDtoCommonName($trackarray['CodecID']); } - if (@$trackarray['SamplingFrequency']) { $track_info['sample_rate'] = $trackarray['SamplingFrequency']; } - if (@$trackarray['Channels']) { $track_info['channels'] = $trackarray['Channels']; } - if (@$trackarray['BitDepth']) { $track_info['bits_per_sample'] = $trackarray['BitDepth']; } - switch (@$trackarray[$this->EBMLidName(EBML_ID_CODECID)]) { - case 'A_PCM/INT/LIT': - case 'A_PCM/INT/BIG': - $track_info['bitrate'] = $trackarray['SamplingFrequency'] * $trackarray['Channels'] * $trackarray['BitDepth']; - break; + } - case 'A_AC3': - if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, false)) { - $ac3_thisfileinfo = array('avdataoffset'=>$ThisFileInfo['matroska']['track_data_offsets'][$trackarray['TrackNumber']]); - $getid3_ac3 = new getid3_ac3($fd, $ac3_thisfileinfo); - $ThisFileInfo['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $ac3_thisfileinfo; - if (!empty($ac3_thisfileinfo['error'])) { - foreach ($ac3_thisfileinfo['error'] as $newerror) { - $this->warnings[] = 'getid3_ac3() says: ['.$newerror.']'; - } - } - if (!empty($ac3_thisfileinfo['warning'])) { - foreach ($ac3_thisfileinfo['warning'] as $newerror) { - $this->warnings[] = 'getid3_ac3() says: ['.$newerror.']'; - } - } - if (isset($ac3_thisfileinfo['audio']) && is_array($ac3_thisfileinfo['audio'])) { - foreach ($ac3_thisfileinfo['audio'] as $key => $value) { - $track_info[$key] = $value; - } - } - unset($ac3_thisfileinfo); - unset($getid3_ac3); - } else { - $this->warnings[] = 'Unable to parse audio data['.__LINE__.'] because cannot include "module.audio.ac3.php"'; - } - break; + private function EnsureBufferHasEnoughData($min_data = 1024) + { + if (($this->current_offset - $this->EBMLbuffer_offset) >= ($this->EBMLbuffer_length - $min_data)) { - case 'A_DTS': - $dts_offset = $ThisFileInfo['matroska']['track_data_offsets'][$trackarray['TrackNumber']]; - // this is a NASTY hack, but sometimes audio data is off by a byte or two and not sure why, email info@getid3.org if you can explain better - fseek($fd, $dts_offset, SEEK_SET); - $magic_test = fread($fd, 8); - for ($i = 0; $i < 4; $i++) { - // look to see if DTS "magic" is here, if so adjust offset by that many bytes - if (substr($magic_test, $i, 4) == "\x7F\xFE\x80\x01") { - $dts_offset += $i; - break; - } - } - if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.dts.php', __FILE__, false)) { - $dts_thisfileinfo = array('avdataoffset'=>$dts_offset); - $getid3_dts = new getid3_dts($fd, $dts_thisfileinfo); - $ThisFileInfo['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $dts_thisfileinfo; - if (!empty($dts_thisfileinfo['error'])) { - foreach ($dts_thisfileinfo['error'] as $newerror) { - $this->warnings[] = 'getid3_dts() says: ['.$newerror.']'; - } - } - if (!empty($dts_thisfileinfo['warning'])) { - foreach ($dts_thisfileinfo['warning'] as $newerror) { - $this->warnings[] = 'getid3_dts() says: ['.$newerror.']'; - } - } - if (isset($dts_thisfileinfo['audio']) && is_array($dts_thisfileinfo['audio'])) { - foreach ($dts_thisfileinfo['audio'] as $key => $value) { - $track_info[$key] = $value; - } - } - unset($dts_thisfileinfo); - unset($getid3_dts); - } else { - $this->warnings[] = 'Unable to parse audio data['.__LINE__.'] because cannot include "module.audio.dts.php"'; - } - break; - - //case 'A_AAC': - // if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.aac.php', __FILE__, false)) { - // $aac_thisfileinfo = array('avdataoffset'=>$ThisFileInfo['matroska']['track_data_offsets'][$trackarray['TrackNumber']]); - // $getid3_aac = new getid3_aac($fd, $aac_thisfileinfo); - // $ThisFileInfo['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $aac_thisfileinfo; - // if (isset($aac_thisfileinfo['audio']) && is_array($aac_thisfileinfo['audio'])) { - // foreach ($aac_thisfileinfo['audio'] as $key => $value) { - // $track_info[$key] = $value; - // } - // } - // unset($aac_thisfileinfo); - // unset($getid3_aac); - // } else { - // $this->warnings[] = 'Unable to parse audio data['.__LINE__.'] because cannot include "module.audio.aac.php"'; - // } - // break; - - case 'A_MPEG/L3': - if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, false)) { - $mp3_thisfileinfo = array( - 'avdataoffset' => $ThisFileInfo['matroska']['track_data_offsets'][$trackarray['TrackNumber']], - 'avdataend' => $ThisFileInfo['matroska']['track_data_offsets'][$trackarray['TrackNumber']] + 1024, - ); - $getid3_mp3 = new getid3_mp3($fd, $mp3_thisfileinfo); - $getid3_mp3->allow_bruteforce = true; - //getid3_mp3::getOnlyMPEGaudioInfo($fd, $mp3_thisfileinfo, $offset, false); - $ThisFileInfo['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $mp3_thisfileinfo; - if (!empty($mp3_thisfileinfo['error'])) { - foreach ($mp3_thisfileinfo['error'] as $newerror) { - $this->warnings[] = 'getid3_mp3() says: ['.$newerror.']'; - } - } - if (!empty($mp3_thisfileinfo['warning'])) { - foreach ($mp3_thisfileinfo['warning'] as $newerror) { - $this->warnings[] = 'getid3_mp3() says: ['.$newerror.']'; - } - } - if (isset($mp3_thisfileinfo['audio']) && is_array($mp3_thisfileinfo['audio'])) { - foreach ($mp3_thisfileinfo['audio'] as $key => $value) { - $track_info[$key] = $value; - } - } - unset($mp3_thisfileinfo); - unset($getid3_mp3); - } else { - $this->warnings[] = 'Unable to parse audio data['.__LINE__.'] because cannot include "module.audio.mp3.php"'; - } - break; - - case 'A_VORBIS': - if (isset($trackarray['CodecPrivate'])) { - // this is a NASTY hack, email info@getid3.org if you have a better idea how to get this info out - $found_vorbis = false; - for ($vorbis_offset = 1; $vorbis_offset < 16; $vorbis_offset++) { - if (substr($trackarray['CodecPrivate'], $vorbis_offset, 6) == 'vorbis') { - $vorbis_offset--; - $found_vorbis = true; - break; - } - } - if ($found_vorbis) { - if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, false)) { - $vorbis_fileinfo = array(); - $oggpageinfo['page_seqno'] = 0; - getid3_ogg::ParseVorbisPageHeader($trackarray['CodecPrivate'], $vorbis_offset, $vorbis_fileinfo, $oggpageinfo); - $ThisFileInfo['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $vorbis_fileinfo; - if (!empty($vorbis_fileinfo['error'])) { - foreach ($vorbis_fileinfo['error'] as $newerror) { - $this->warnings[] = 'getid3_ogg() says: ['.$newerror.']'; - } - } - if (!empty($vorbis_fileinfo['warning'])) { - foreach ($vorbis_fileinfo['warning'] as $newerror) { - $this->warnings[] = 'getid3_ogg() says: ['.$newerror.']'; - } - } - if (isset($vorbis_fileinfo['audio']) && is_array($vorbis_fileinfo['audio'])) { - foreach ($vorbis_fileinfo['audio'] as $key => $value) { - $track_info[$key] = $value; - } - } - if (@$vorbis_fileinfo['ogg']['bitrate_average']) { - $track_info['bitrate'] = $vorbis_fileinfo['ogg']['bitrate_average']; - } elseif (@$vorbis_fileinfo['ogg']['bitrate_nominal']) { - $track_info['bitrate'] = $vorbis_fileinfo['ogg']['bitrate_nominal']; - } - unset($vorbis_fileinfo); - unset($oggpageinfo); - } else { - $this->warnings[] = 'Unable to parse audio data['.__LINE__.'] because cannot include "module.audio.ogg.php"'; - } - } else { - } - } else { - } - break; - - default: - $this->warnings[] = 'Unhandled audio type "'.@$trackarray[$this->EBMLidName(EBML_ID_CODECID)].'"'; - break; - } - - - $ThisFileInfo['audio']['streams'][] = $track_info; - if (isset($track_info['dataformat']) && empty($ThisFileInfo['audio']['dataformat'])) { - foreach ($track_info as $key => $value) { - $ThisFileInfo['audio'][$key] = $value; - } - } - break; - default: - // ignore, do nothing - break; - } + if (!getid3_lib::intValueSupported($this->current_offset + $this->getid3->fread_buffer_size())) { + $this->getid3->info['error'][] = 'EBML parser: cannot read past '.$this->current_offset; + return false; } - } - if ($this->hide_clusters) { - // too much data returned that is usually not useful - if (isset($ThisFileInfo['matroska']['segments']) && is_array($ThisFileInfo['matroska']['segments'])) { - foreach ($ThisFileInfo['matroska']['segments'] as $key => $segmentsarray) { - if ($segmentsarray['id'] == EBML_ID_CLUSTER) { - unset($ThisFileInfo['matroska']['segments'][$key]); - } - } + fseek($this->getid3->fp, $this->current_offset, SEEK_SET); + $this->EBMLbuffer_offset = $this->current_offset; + $this->EBMLbuffer = fread($this->getid3->fp, max($min_data, $this->getid3->fread_buffer_size())); + $this->EBMLbuffer_length = strlen($this->EBMLbuffer); + + if ($this->EBMLbuffer_length == 0 && feof($this->getid3->fp)) { + $this->getid3->info['error'][] = 'EBML parser: ran out of file at offset '.$this->current_offset; + return false; } - if (isset($ThisFileInfo['matroska']['seek']) && is_array($ThisFileInfo['matroska']['seek'])) { - foreach ($ThisFileInfo['matroska']['seek'] as $key => $seekarray) { - if ($seekarray['target_id'] == EBML_ID_CLUSTER) { - unset($ThisFileInfo['matroska']['seek'][$key]); - } - } - } - //unset($ThisFileInfo['matroska']['cluster']); - //unset($ThisFileInfo['matroska']['track_data_offsets']); - } - - if (!empty($ThisFileInfo['video']['streams'])) { - $ThisFileInfo['mime_type'] = 'video/x-matroska'; - } elseif (!empty($ThisFileInfo['video']['streams'])) { - $ThisFileInfo['mime_type'] = 'audio/x-matroska'; - } elseif (isset($ThisFileInfo['mime_type'])) { - unset($ThisFileInfo['mime_type']); - } - - foreach ($this->warnings as $key => $value) { - $ThisFileInfo['warning'][] = $value; } return true; } + private function readEBMLint() + { + $actual_offset = $this->current_offset - $this->EBMLbuffer_offset; -/////////////////////////////////////// - - - function EnsureBufferHasEnoughData(&$fd, &$EBMLdata, &$offset, &$EBMLdata_offset) { - $min_data = 1024; - if ($offset > 2147450880) { // 2^31 - 2^15 (2G-32k) - $offset = pow(2,63); - return false; - } elseif (($offset - $EBMLdata_offset) >= (strlen($EBMLdata) - $min_data)) { - fseek($fd, $offset, SEEK_SET); - $EBMLdata_offset = ftell($fd); - $EBMLdata = fread($fd, $this->read_buffer_size); - } - return true; - } - - function readEBMLint(&$string, &$offset, $dataoffset=0) { - $actual_offset = $offset - $dataoffset; - if ($offset > 2147450880) { // 2^31 - 2^15 (2G-32k) - $this->warnings[] = 'aborting readEBMLint() because $offset larger than 2GB'; - return false; - } elseif ($actual_offset >= strlen($string)) { - $this->warnings[] = '$actual_offset > $string in readEBMLint($string['.strlen($string).'], '.$offset.', '.$dataoffset.')'; - return false; - } elseif ($actual_offset < 0) { - $this->warnings[] = '$actual_offset < 0 in readEBMLint($string['.strlen($string).'], '.$offset.', '.$dataoffset.')'; - return false; - } - $first_byte_int = ord($string{$actual_offset}); + // get length of integer + $first_byte_int = ord($this->EBMLbuffer[$actual_offset]); if (0x80 & $first_byte_int) { $length = 1; } elseif (0x40 & $first_byte_int) { @@ -1409,16 +1208,175 @@ class getid3_matroska } elseif (0x01 & $first_byte_int) { $length = 8; } else { - $offset = pow(2,63); // abort processing, skip to end of file - $this->warnings[] = 'invalid EBML integer (leading 0x00) at '.$offset; - return false; + throw new Exception('invalid EBML integer (leading 0x00) at '.$this->current_offset); } - $int_value = $this->EBML2Int(substr($string, $actual_offset, $length)); - $offset += $length; + + // read + $int_value = self::EBML2Int(substr($this->EBMLbuffer, $actual_offset, $length)); + $this->current_offset += $length; + return $int_value; } - function EBML2Int($EBMLstring) { + private function readEBMLelementData($length) + { + $data = substr($this->EBMLbuffer, $this->current_offset - $this->EBMLbuffer_offset, $length); + $this->current_offset += $length; + + return $data; + } + + private function getEBMLelement(&$element, $parent_end, $get_data = false) + { + if ($this->current_offset >= $parent_end) { + return false; + } + + if (!$this->EnsureBufferHasEnoughData()) { + $this->current_offset = PHP_INT_MAX; // do not exit parser right now, allow to finish current loop to gather maximum information + return false; + } + + $element = array(); + + // set offset + $element['offset'] = $this->current_offset; + + // get ID + $element['id'] = $this->readEBMLint(); + + // get name + $element['id_name'] = self::EBMLidName($element['id']); + + // get length + $element['length'] = $this->readEBMLint(); + + // get end offset + $element['end'] = $this->current_offset + $element['length']; + + // get raw data + $dont_parse = (in_array($element['id'], $this->unuseful_elements) || $element['id_name'] == dechex($element['id'])); + if (($get_data === true || (is_array($get_data) && !in_array($element['id'], $get_data))) && !$dont_parse) { + $element['data'] = $this->readEBMLelementData($element['length'], $element); + } + + return true; + } + + private function unhandledElement($type, $line, $element) + { + // warn only about unknown and missed elements, not about unuseful + if (!in_array($element['id'], $this->unuseful_elements)) { + $this->getid3->warning('Unhandled '.$type.' element ['.basename(__FILE__).':'.$line.'] ('.$element['id'].'::'.$element['id_name'].' ['.$element['length'].' bytes]) at '.$element['offset']); + } + + // increase offset for unparsed elements + if (!isset($element['data'])) { + $this->current_offset = $element['end']; + } + } + + private function ExtractCommentsSimpleTag($SimpleTagArray) + { + if (!empty($SimpleTagArray['SimpleTag'])) { + foreach ($SimpleTagArray['SimpleTag'] as $SimpleTagKey => $SimpleTagData) { + if (!empty($SimpleTagData['TagName']) && !empty($SimpleTagData['TagString'])) { + $this->getid3->info['matroska']['comments'][strtolower($SimpleTagData['TagName'])][] = $SimpleTagData['TagString']; + } + if (!empty($SimpleTagData['SimpleTag'])) { + $this->ExtractCommentsSimpleTag($SimpleTagData); + } + } + } + + return true; + } + + private function HandleEMBLSimpleTag($parent_end) + { + $simpletag_entry = array(); + + while ($this->getEBMLelement($element, $parent_end, array(EBML_ID_SIMPLETAG))) { + switch ($element['id']) { + + case EBML_ID_TAGNAME: + case EBML_ID_TAGLANGUAGE: + case EBML_ID_TAGSTRING: + case EBML_ID_TAGBINARY: + $simpletag_entry[$element['id_name']] = $element['data']; + break; + + case EBML_ID_SIMPLETAG: + $simpletag_entry[$element['id_name']][] = $this->HandleEMBLSimpleTag($element['end']); + break; + + case EBML_ID_TAGDEFAULT: + $simpletag_entry[$element['id_name']] = (bool)getid3_lib::BigEndian2Int($element['data']); + break; + + default: + $this->unhandledElement('tag.simpletag', __LINE__, $element); + } + } + + return $simpletag_entry; + } + + private function HandleEMBLClusterBlock($element, $block_type, &$info) + { + // http://www.matroska.org/technical/specs/index.html#block_structure + // http://www.matroska.org/technical/specs/index.html#simpleblock_structure + + $cluster_block_data = array(); + $cluster_block_data['tracknumber'] = $this->readEBMLint(); + $cluster_block_data['timecode'] = getid3_lib::BigEndian2Int($this->readEBMLelementData(2)); + $cluster_block_data['flags_raw'] = getid3_lib::BigEndian2Int($this->readEBMLelementData(1)); + + if ($block_type == EBML_ID_CLUSTERSIMPLEBLOCK) { + $cluster_block_data['flags']['keyframe'] = (($cluster_block_data['flags_raw'] & 0x80) >> 7); + //$cluster_block_data['flags']['reserved1'] = (($cluster_block_data['flags_raw'] & 0x70) >> 4); + } + else { + //$cluster_block_data['flags']['reserved1'] = (($cluster_block_data['flags_raw'] & 0xF0) >> 4); + } + $cluster_block_data['flags']['invisible'] = (bool)(($cluster_block_data['flags_raw'] & 0x08) >> 3); + $cluster_block_data['flags']['lacing'] = (($cluster_block_data['flags_raw'] & 0x06) >> 1); // 00=no lacing; 01=Xiph lacing; 11=EBML lacing; 10=fixed-size lacing + if ($block_type == EBML_ID_CLUSTERSIMPLEBLOCK) { + $cluster_block_data['flags']['discardable'] = (($cluster_block_data['flags_raw'] & 0x01)); + } + else { + //$cluster_block_data['flags']['reserved2'] = (($cluster_block_data['flags_raw'] & 0x01) >> 0); + } + $cluster_block_data['flags']['lacing_type'] = self::MatroskaBlockLacingType($cluster_block_data['flags']['lacing']); + + // Lace (when lacing bit is set) + if ($cluster_block_data['flags']['lacing'] > 0) { + $cluster_block_data['lace_frames'] = getid3_lib::BigEndian2Int($this->readEBMLelementData(1)) + 1; // Number of frames in the lace-1 (uint8) + if ($cluster_block_data['flags']['lacing'] != 0x02) { // Lace-coded size of each frame of the lace, except for the last one (multiple uint8). *This is not used with Fixed-size lacing as it is calculated automatically from (total size of lace) / (number of frames in lace). + for ($i = 1; $i < $cluster_block_data['lace_frames']; $i ++) { + if ($cluster_block_data['flags']['lacing'] == 0x03) { // EBML lacing + // TODO: read size correctly, calc size for the last frame. For now offsets are deteminded OK with readEBMLint() and that's the most important thing. + $cluster_block_data['lace_frames_size'][$i] = $this->readEBMLint(); + } + else { // Xiph lacing + $cluster_block_data['lace_frames_size'][$i] = getid3_lib::BigEndian2Int($this->readEBMLelementData(1)); + } + } + } + } + + if (!isset($info['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']])) { + $info['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']]['offset'] = $this->current_offset; + $info['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']]['length'] = $element['end'] - $this->current_offset; + } + + // set offset manually + $this->current_offset = $element['end']; + + return $cluster_block_data; + } + + private static function EBML2Int($EBMLstring) { // http://matroska.org/specs/ // Element ID coded with an UTF-8 like system: @@ -1438,38 +1396,50 @@ class getid3_matroska // 0000 001x xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^49-2 // 0000 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^56-2 - $first_byte_int = ord($EBMLstring{0}); + $first_byte_int = ord($EBMLstring[0]); if (0x80 & $first_byte_int) { - $EBMLstring{0} = chr($first_byte_int & 0x7F); + $EBMLstring[0] = chr($first_byte_int & 0x7F); } elseif (0x40 & $first_byte_int) { - $EBMLstring{0} = chr($first_byte_int & 0x3F); + $EBMLstring[0] = chr($first_byte_int & 0x3F); } elseif (0x20 & $first_byte_int) { - $EBMLstring{0} = chr($first_byte_int & 0x1F); + $EBMLstring[0] = chr($first_byte_int & 0x1F); } elseif (0x10 & $first_byte_int) { - $EBMLstring{0} = chr($first_byte_int & 0x0F); + $EBMLstring[0] = chr($first_byte_int & 0x0F); } elseif (0x08 & $first_byte_int) { - $EBMLstring{0} = chr($first_byte_int & 0x07); + $EBMLstring[0] = chr($first_byte_int & 0x07); } elseif (0x04 & $first_byte_int) { - $EBMLstring{0} = chr($first_byte_int & 0x03); + $EBMLstring[0] = chr($first_byte_int & 0x03); } elseif (0x02 & $first_byte_int) { - $EBMLstring{0} = chr($first_byte_int & 0x01); + $EBMLstring[0] = chr($first_byte_int & 0x01); } elseif (0x01 & $first_byte_int) { - $EBMLstring{0} = chr($first_byte_int & 0x00); - } else { - return false; + $EBMLstring[0] = chr($first_byte_int & 0x00); } + return getid3_lib::BigEndian2Int($EBMLstring); } - - function EBMLdate2unix($EBMLdatestamp) { + private static function EBMLdate2unix($EBMLdatestamp) { // Date - signed 8 octets integer in nanoseconds with 0 indicating the precise beginning of the millennium (at 2001-01-01T00:00:00,000000000 UTC) // 978307200 == mktime(0, 0, 0, 1, 1, 2001) == January 1, 2001 12:00:00am UTC return round(($EBMLdatestamp / 1000000000) + 978307200); } + public static function MatroskaTargetTypeValue($target_type) { + // http://www.matroska.org/technical/specs/tagging/index.html + static $MatroskaTargetTypeValue = array(); + if (empty($MatroskaTargetTypeValue)) { + $MatroskaTargetTypeValue[10] = 'A: ~ V:shot'; // the lowest hierarchy found in music or movies + $MatroskaTargetTypeValue[20] = 'A:subtrack/part/movement ~ V:scene'; // corresponds to parts of a track for audio (like a movement) + $MatroskaTargetTypeValue[30] = 'A:track/song ~ V:chapter'; // the common parts of an album or a movie + $MatroskaTargetTypeValue[40] = 'A:part/session ~ V:part/session'; // when an album or episode has different logical parts + $MatroskaTargetTypeValue[50] = 'A:album/opera/concert ~ V:movie/episode/concert'; // the most common grouping level of music and video (equals to an episode for TV series) + $MatroskaTargetTypeValue[60] = 'A:edition/issue/volume/opus ~ V:season/sequel/volume'; // a list of lower levels grouped together + $MatroskaTargetTypeValue[70] = 'A:collection ~ V:collection'; // the high hierarchy consisting of many different lower items + } + return (isset($MatroskaTargetTypeValue[$target_type]) ? $MatroskaTargetTypeValue[$target_type] : $target_type); + } - function MatroskaBlockLacingType($lacingtype) { + public static function MatroskaBlockLacingType($lacingtype) { // http://matroska.org/technical/specs/index.html#block_structure static $MatroskaBlockLacingType = array(); if (empty($MatroskaBlockLacingType)) { @@ -1481,7 +1451,7 @@ class getid3_matroska return (isset($MatroskaBlockLacingType[$lacingtype]) ? $MatroskaBlockLacingType[$lacingtype] : $lacingtype); } - function MatroskaCodecIDtoCommonName($codecid) { + public static function MatroskaCodecIDtoCommonName($codecid) { // http://www.matroska.org/technical/specs/codecid/index.html static $MatroskaCodecIDlist = array(); if (empty($MatroskaCodecIDlist)) { @@ -1509,18 +1479,20 @@ class getid3_matroska $MatroskaCodecIDlist['V_MPEG4/ISO/ASP'] = 'mpeg4'; $MatroskaCodecIDlist['V_MPEG4/ISO/AVC'] = 'h264'; $MatroskaCodecIDlist['V_MPEG4/ISO/SP'] = 'mpeg4'; + $MatroskaCodecIDlist['V_VP8'] = 'vp8'; + $MatroskaCodecIDlist['V_MS/VFW/FOURCC'] = 'riff'; + $MatroskaCodecIDlist['A_MS/ACM'] = 'riff'; } return (isset($MatroskaCodecIDlist[$codecid]) ? $MatroskaCodecIDlist[$codecid] : $codecid); } - function EBMLidName($value) { + private static function EBMLidName($value) { static $EBMLidList = array(); if (empty($EBMLidList)) { $EBMLidList[EBML_ID_ASPECTRATIOTYPE] = 'AspectRatioType'; $EBMLidList[EBML_ID_ATTACHEDFILE] = 'AttachedFile'; $EBMLidList[EBML_ID_ATTACHMENTLINK] = 'AttachmentLink'; $EBMLidList[EBML_ID_ATTACHMENTS] = 'Attachments'; - $EBMLidList[EBML_ID_ATTACHMENTUID] = 'AttachmentUID'; $EBMLidList[EBML_ID_AUDIO] = 'Audio'; $EBMLidList[EBML_ID_BITDEPTH] = 'BitDepth'; $EBMLidList[EBML_ID_CHANNELPOSITIONS] = 'ChannelPositions'; @@ -1623,6 +1595,7 @@ class getid3_matroska $EBMLidList[EBML_ID_DOCTYPEREADVERSION] = 'DocTypeReadVersion'; $EBMLidList[EBML_ID_DOCTYPEVERSION] = 'DocTypeVersion'; $EBMLidList[EBML_ID_DURATION] = 'Duration'; + $EBMLidList[EBML_ID_EBML] = 'EBML'; $EBMLidList[EBML_ID_EBMLMAXIDLENGTH] = 'EBMLMaxIDLength'; $EBMLidList[EBML_ID_EBMLMAXSIZELENGTH] = 'EBMLMaxSizeLength'; $EBMLidList[EBML_ID_EBMLREADVERSION] = 'EBMLReadVersion'; @@ -1667,6 +1640,7 @@ class getid3_matroska $EBMLidList[EBML_ID_SEEKHEAD] = 'SeekHead'; $EBMLidList[EBML_ID_SEEKID] = 'SeekID'; $EBMLidList[EBML_ID_SEEKPOSITION] = 'SeekPosition'; + $EBMLidList[EBML_ID_SEGMENT] = 'Segment'; $EBMLidList[EBML_ID_SEGMENTFAMILY] = 'SegmentFamily'; $EBMLidList[EBML_ID_SEGMENTFILENAME] = 'SegmentFilename'; $EBMLidList[EBML_ID_SEGMENTUID] = 'SegmentUID'; @@ -1674,6 +1648,7 @@ class getid3_matroska $EBMLidList[EBML_ID_CLUSTERSLICES] = 'ClusterSlices'; $EBMLidList[EBML_ID_STEREOMODE] = 'StereoMode'; $EBMLidList[EBML_ID_TAG] = 'Tag'; + $EBMLidList[EBML_ID_TAGATTACHMENTUID] = 'TagAttachmentUID'; $EBMLidList[EBML_ID_TAGBINARY] = 'TagBinary'; $EBMLidList[EBML_ID_TAGCHAPTERUID] = 'TagChapterUID'; $EBMLidList[EBML_ID_TAGDEFAULT] = 'TagDefault'; @@ -1704,8 +1679,27 @@ class getid3_matroska $EBMLidList[EBML_ID_VOID] = 'Void'; $EBMLidList[EBML_ID_WRITINGAPP] = 'WritingApp'; } + return (isset($EBMLidList[$value]) ? $EBMLidList[$value] : dechex($value)); } + + private static function getDefaultStreamInfo($streams) + { + foreach (array_reverse($streams) as $stream) { + if ($stream['default']) { + break; + } + } + unset($stream['default']); + if (isset($stream['name'])) { + unset($stream['name']); + } + + $info = $stream; + $info['streams'] = $streams; + + return $info; + } } diff --git a/apps/media/getID3/getid3/module.audio-video.mpeg.php b/3rdparty/getid3/module.audio-video.mpeg.php similarity index 60% rename from apps/media/getID3/getid3/module.audio-video.mpeg.php rename to 3rdparty/getid3/module.audio-video.mpeg.php index 8f48784849..499b740c39 100644 --- a/apps/media/getID3/getid3/module.audio-video.mpeg.php +++ b/3rdparty/getid3/module.audio-video.mpeg.php @@ -25,17 +25,19 @@ define('GETID3_MPEG_VIDEO_GROUP_START', "\x00\x00\x01\xB8"); define('GETID3_MPEG_AUDIO_START', "\x00\x00\x01\xC0"); -class getid3_mpeg +class getid3_mpeg extends getid3_handler { - function getid3_mpeg(&$fd, &$ThisFileInfo) { - if ($ThisFileInfo['avdataend'] <= $ThisFileInfo['avdataoffset']) { - $ThisFileInfo['error'][] = '"avdataend" ('.$ThisFileInfo['avdataend'].') is unexpectedly less-than-or-equal-to "avdataoffset" ('.$ThisFileInfo['avdataoffset'].')'; + 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; } - $ThisFileInfo['fileformat'] = 'mpeg'; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $MPEGstreamData = fread($fd, min(100000, $ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'])); + $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; @@ -62,7 +64,7 @@ class getid3_mpeg // non-intra quant. matrix flag 1 bit // non-intra quant. matrix values 512 bits (present if matrix flag == 1) - $ThisFileInfo['video']['dataformat'] = 'mpeg'; + $info['video']['dataformat'] = 'mpeg'; $VideoChunkOffset += (strlen(GETID3_MPEG_VIDEO_SEQUENCE_HEADER) - 1); @@ -75,71 +77,71 @@ class getid3_mpeg $assortedinformation = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 4)); $VideoChunkOffset += 4; - $ThisFileInfo['mpeg']['video']['raw']['framesize_horizontal'] = ($FrameSizeDWORD & 0xFFF000) >> 12; // 12 bits for horizontal frame size - $ThisFileInfo['mpeg']['video']['raw']['framesize_vertical'] = ($FrameSizeDWORD & 0x000FFF); // 12 bits for vertical frame size - $ThisFileInfo['mpeg']['video']['raw']['pixel_aspect_ratio'] = ($AspectRatioFrameRateDWORD & 0xF0) >> 4; - $ThisFileInfo['mpeg']['video']['raw']['frame_rate'] = ($AspectRatioFrameRateDWORD & 0x0F); + $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); - $ThisFileInfo['mpeg']['video']['framesize_horizontal'] = $ThisFileInfo['mpeg']['video']['raw']['framesize_horizontal']; - $ThisFileInfo['mpeg']['video']['framesize_vertical'] = $ThisFileInfo['mpeg']['video']['raw']['framesize_vertical']; + $info['mpeg']['video']['framesize_horizontal'] = $info['mpeg']['video']['raw']['framesize_horizontal']; + $info['mpeg']['video']['framesize_vertical'] = $info['mpeg']['video']['raw']['framesize_vertical']; - $ThisFileInfo['mpeg']['video']['pixel_aspect_ratio'] = $this->MPEGvideoAspectRatioLookup($ThisFileInfo['mpeg']['video']['raw']['pixel_aspect_ratio']); - $ThisFileInfo['mpeg']['video']['pixel_aspect_ratio_text'] = $this->MPEGvideoAspectRatioTextLookup($ThisFileInfo['mpeg']['video']['raw']['pixel_aspect_ratio']); - $ThisFileInfo['mpeg']['video']['frame_rate'] = $this->MPEGvideoFramerateLookup($ThisFileInfo['mpeg']['video']['raw']['frame_rate']); + $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']); - $ThisFileInfo['mpeg']['video']['raw']['bitrate'] = getid3_lib::Bin2Dec(substr($assortedinformation, 0, 18)); - $ThisFileInfo['mpeg']['video']['raw']['marker_bit'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 18, 1)); - $ThisFileInfo['mpeg']['video']['raw']['vbv_buffer_size'] = getid3_lib::Bin2Dec(substr($assortedinformation, 19, 10)); - $ThisFileInfo['mpeg']['video']['raw']['constrained_param_flag'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 29, 1)); - $ThisFileInfo['mpeg']['video']['raw']['intra_quant_flag'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 30, 1)); - if ($ThisFileInfo['mpeg']['video']['raw']['intra_quant_flag']) { + $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 - $ThisFileInfo['mpeg']['video']['raw']['intra_quant'] = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 64)); + $info['mpeg']['video']['raw']['intra_quant'] = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 64)); $VideoChunkOffset += 64; - $ThisFileInfo['mpeg']['video']['raw']['non_intra_quant_flag'] = (bool) getid3_lib::Bin2Dec(substr($ThisFileInfo['mpeg']['video']['raw']['intra_quant'], 511, 1)); - $ThisFileInfo['mpeg']['video']['raw']['intra_quant'] = getid3_lib::Bin2Dec(substr($assortedinformation, 31, 1)).substr(getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 64)), 0, 511); + $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 ($ThisFileInfo['mpeg']['video']['raw']['non_intra_quant_flag']) { - $ThisFileInfo['mpeg']['video']['raw']['non_intra_quant'] = substr($MPEGstreamData, $VideoChunkOffset, 64); + if ($info['mpeg']['video']['raw']['non_intra_quant_flag']) { + $info['mpeg']['video']['raw']['non_intra_quant'] = substr($MPEGstreamData, $VideoChunkOffset, 64); $VideoChunkOffset += 64; } } else { - $ThisFileInfo['mpeg']['video']['raw']['non_intra_quant_flag'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 31, 1)); - if ($ThisFileInfo['mpeg']['video']['raw']['non_intra_quant_flag']) { - $ThisFileInfo['mpeg']['video']['raw']['non_intra_quant'] = substr($MPEGstreamData, $VideoChunkOffset, 64); + $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 ($ThisFileInfo['mpeg']['video']['raw']['bitrate'] == 0x3FFFF) { // 18 set bits + if ($info['mpeg']['video']['raw']['bitrate'] == 0x3FFFF) { // 18 set bits - $ThisFileInfo['warning'][] = 'This version of getID3() ['.GETID3_VERSION.'] cannot determine average bitrate of VBR MPEG video files'; - $ThisFileInfo['mpeg']['video']['bitrate_mode'] = 'vbr'; + $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 { - $ThisFileInfo['mpeg']['video']['bitrate'] = $ThisFileInfo['mpeg']['video']['raw']['bitrate'] * 400; - $ThisFileInfo['mpeg']['video']['bitrate_mode'] = 'cbr'; - $ThisFileInfo['video']['bitrate'] = $ThisFileInfo['mpeg']['video']['bitrate']; + $info['mpeg']['video']['bitrate'] = $info['mpeg']['video']['raw']['bitrate'] * 400; + $info['mpeg']['video']['bitrate_mode'] = 'cbr'; + $info['video']['bitrate'] = $info['mpeg']['video']['bitrate']; } - $ThisFileInfo['video']['resolution_x'] = $ThisFileInfo['mpeg']['video']['framesize_horizontal']; - $ThisFileInfo['video']['resolution_y'] = $ThisFileInfo['mpeg']['video']['framesize_vertical']; - $ThisFileInfo['video']['frame_rate'] = $ThisFileInfo['mpeg']['video']['frame_rate']; - $ThisFileInfo['video']['bitrate_mode'] = $ThisFileInfo['mpeg']['video']['bitrate_mode']; - $ThisFileInfo['video']['pixel_aspect_ratio'] = $ThisFileInfo['mpeg']['video']['pixel_aspect_ratio']; - $ThisFileInfo['video']['lossless'] = false; - $ThisFileInfo['video']['bits_per_sample'] = 24; + $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 { - $ThisFileInfo['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?'; + $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?'; } @@ -151,9 +153,9 @@ class getid3_mpeg //difference between MPEG-1 and MPEG-2 video streams. if (substr($MPEGstreamData, $VideoChunkOffset, 4) == GETID3_MPEG_VIDEO_EXTENSION_START) { - $ThisFileInfo['video']['codec'] = 'MPEG-2'; + $info['video']['codec'] = 'MPEG-2'; } else { - $ThisFileInfo['video']['codec'] = 'MPEG-1'; + $info['video']['codec'] = 'MPEG-1'; } @@ -165,33 +167,38 @@ class getid3_mpeg } } + $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 - - $dummy = $ThisFileInfo; - if (getid3_mp3::decodeMPEGaudioHeader($fd, ($AudioChunkOffset + 3) + 8 + $i, $dummy, false)) { - $ThisFileInfo = $dummy; - $ThisFileInfo['audio']['bitrate_mode'] = 'cbr'; - $ThisFileInfo['audio']['lossless'] = false; + 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($ThisFileInfo['video']['bitrate']) && !empty($ThisFileInfo['audio']['bitrate'])) { - $ThisFileInfo['playtime_seconds'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / ($ThisFileInfo['video']['bitrate'] + $ThisFileInfo['audio']['bitrate']); + 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 - $ThisFileInfo['playtime_seconds'] *= $this->MPEGsystemNonOverheadPercentage($ThisFileInfo['video']['bitrate'], $ThisFileInfo['audio']['bitrate']); + $info['playtime_seconds'] *= $this->MPEGsystemNonOverheadPercentage($info['video']['bitrate'], $info['audio']['bitrate']); - //switch ($ThisFileInfo['video']['bitrate']) { + //switch ($info['video']['bitrate']) { // case('5000000'): // $multiplier = 0.93292642112380355828048824319889; // break; @@ -208,10 +215,10 @@ class getid3_mpeg // $multiplier = 1; // break; //} - //$ThisFileInfo['playtime_seconds'] *= $multiplier; - //$ThisFileInfo['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 ($ThisFileInfo['video']['bitrate'] < 50000) { - $ThisFileInfo['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.'; + //$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.'; } } diff --git a/3rdparty/getid3/module.audio-video.nsv.php b/3rdparty/getid3/module.audio-video.nsv.php new file mode 100644 index 0000000000..5a587e6720 --- /dev/null +++ b/3rdparty/getid3/module.audio-video.nsv.php @@ -0,0 +1,226 @@ + // +// 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 +{ + + 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; + } + + 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; + } + + 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; + } + + + 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); + } + +} + + +?> \ No newline at end of file diff --git a/3rdparty/getid3/module.audio-video.quicktime.php b/3rdparty/getid3/module.audio-video.quicktime.php new file mode 100644 index 0000000000..3e96ea1a0c --- /dev/null +++ b/3rdparty/getid3/module.audio-video.quicktime.php @@ -0,0 +1,2134 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio-video.quicktime.php // +// module for analyzing Quicktime and MP3-in-MP4 files // +// dependencies: module.audio.mp3.php // +// /// +///////////////////////////////////////////////////////////////// + +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true); + +class getid3_quicktime extends getid3_handler +{ + + var $ReturnAtomData = true; + var $ParseAllPossibleAtoms = false; + + function Analyze() { + $info = &$this->getid3->info; + + $info['fileformat'] = 'quicktime'; + $info['quicktime']['hinting'] = false; + $info['quicktime']['controller'] = 'standard'; // may be overridden if 'ctyp' atom is present + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + + $offset = 0; + $atomcounter = 0; + + while ($offset < $info['avdataend']) { + if (!getid3_lib::intValueSupported($offset)) { + $info['error'][] = 'Unable to parse atom at offset '.$offset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions'; + break; + } + fseek($this->getid3->fp, $offset, SEEK_SET); + $AtomHeader = fread($this->getid3->fp, 8); + + $atomsize = getid3_lib::BigEndian2Int(substr($AtomHeader, 0, 4)); + $atomname = substr($AtomHeader, 4, 4); + + // 64-bit MOV patch by jlegateØktnc*com + if ($atomsize == 1) { + $atomsize = getid3_lib::BigEndian2Int(fread($this->getid3->fp, 8)); + } + + $info['quicktime'][$atomname]['name'] = $atomname; + $info['quicktime'][$atomname]['size'] = $atomsize; + $info['quicktime'][$atomname]['offset'] = $offset; + + if (($offset + $atomsize) > $info['avdataend']) { + $info['error'][] = 'Atom at offset '.$offset.' claims to go beyond end-of-file (length: '.$atomsize.' bytes)'; + return false; + } + + if ($atomsize == 0) { + // Furthermore, for historical reasons the list of atoms is optionally + // terminated by a 32-bit integer set to 0. If you are writing a program + // to read user data atoms, you should allow for the terminating 0. + break; + } + switch ($atomname) { + case 'mdat': // Media DATa atom + // 'mdat' contains the actual data for the audio/video + if (($atomsize > 8) && (!isset($info['avdataend_tmp']) || ($info['quicktime'][$atomname]['size'] > ($info['avdataend_tmp'] - $info['avdataoffset'])))) { + + $info['avdataoffset'] = $info['quicktime'][$atomname]['offset'] + 8; + $OldAVDataEnd = $info['avdataend']; + $info['avdataend'] = $info['quicktime'][$atomname]['offset'] + $info['quicktime'][$atomname]['size']; + + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; + $getid3_temp->info['avdataend'] = $info['avdataend']; + $getid3_mp3 = new getid3_mp3($getid3_temp); + if ($getid3_mp3->MPEGaudioHeaderValid($getid3_mp3->MPEGaudioHeaderDecode(fread($this->getid3->fp, 4)))) { + $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false); + if (!empty($getid3_temp->info['warning'])) { + foreach ($getid3_temp->info['warning'] as $value) { + $info['warning'][] = $value; + } + } + if (!empty($getid3_temp->info['mpeg'])) { + $info['mpeg'] = $getid3_temp->info['mpeg']; + if (isset($info['mpeg']['audio'])) { + $info['audio']['dataformat'] = 'mp3'; + $info['audio']['codec'] = (!empty($info['mpeg']['audio']['encoder']) ? $info['mpeg']['audio']['encoder'] : (!empty($info['mpeg']['audio']['codec']) ? $info['mpeg']['audio']['codec'] : (!empty($info['mpeg']['audio']['LAME']) ? 'LAME' :'mp3'))); + $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; + $info['audio']['channels'] = $info['mpeg']['audio']['channels']; + $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; + $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); + $info['bitrate'] = $info['audio']['bitrate']; + } + } + } + unset($getid3_mp3, $getid3_temp); + $info['avdataend'] = $OldAVDataEnd; + unset($OldAVDataEnd); + + } + break; + + case 'free': // FREE space atom + case 'skip': // SKIP atom + case 'wide': // 64-bit expansion placeholder atom + // 'free', 'skip' and 'wide' are just padding, contains no useful data at all + break; + + default: + $atomHierarchy = array(); + $info['quicktime'][$atomname] = $this->QuicktimeParseAtom($atomname, $atomsize, fread($this->getid3->fp, $atomsize), $offset, $atomHierarchy, $this->ParseAllPossibleAtoms); + break; + } + + $offset += $atomsize; + $atomcounter++; + } + + if (!empty($info['avdataend_tmp'])) { + // this value is assigned to a temp value and then erased because + // otherwise any atoms beyond the 'mdat' atom would not get parsed + $info['avdataend'] = $info['avdataend_tmp']; + unset($info['avdataend_tmp']); + } + + if (!isset($info['bitrate']) && isset($info['playtime_seconds'])) { + $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; + } + if (isset($info['bitrate']) && !isset($info['audio']['bitrate']) && !isset($info['quicktime']['video'])) { + $info['audio']['bitrate'] = $info['bitrate']; + } + if (!empty($info['playtime_seconds']) && !isset($info['video']['frame_rate']) && !empty($info['quicktime']['stts_framecount'])) { + foreach ($info['quicktime']['stts_framecount'] as $key => $samples_count) { + $samples_per_second = $samples_count / $info['playtime_seconds']; + if ($samples_per_second > 240) { + // has to be audio samples + } else { + $info['video']['frame_rate'] = $samples_per_second; + break; + } + } + } + if (($info['audio']['dataformat'] == 'mp4') && empty($info['video']['resolution_x'])) { + $info['fileformat'] = 'mp4'; + $info['mime_type'] = 'audio/mp4'; + unset($info['video']['dataformat']); + } + + if (!$this->ReturnAtomData) { + unset($info['quicktime']['moov']); + } + + if (empty($info['audio']['dataformat']) && !empty($info['quicktime']['audio'])) { + $info['audio']['dataformat'] = 'quicktime'; + } + if (empty($info['video']['dataformat']) && !empty($info['quicktime']['video'])) { + $info['video']['dataformat'] = 'quicktime'; + } + + return true; + } + + function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) { + // http://developer.apple.com/techpubs/quicktime/qtdevdocs/APIREF/INDEX/atomalphaindex.htm + + $info = &$this->getid3->info; + + $atom_parent = array_pop($atomHierarchy); + array_push($atomHierarchy, $atomname); + $atom_structure['hierarchy'] = implode(' ', $atomHierarchy); + $atom_structure['name'] = $atomname; + $atom_structure['size'] = $atomsize; + $atom_structure['offset'] = $baseoffset; +//echo getid3_lib::PrintHexBytes(substr($atom_data, 0, 8)).'
'; +//echo getid3_lib::PrintHexBytes(substr($atom_data, 0, 8), false).'

'; + switch ($atomname) { + case 'moov': // MOVie container atom + case 'trak': // TRAcK container atom + case 'clip': // CLIPping container atom + case 'matt': // track MATTe container atom + case 'edts': // EDiTS container atom + case 'tref': // Track REFerence container atom + case 'mdia': // MeDIA container atom + case 'minf': // Media INFormation container atom + case 'dinf': // Data INFormation container atom + case 'udta': // User DaTA container atom + case 'cmov': // Compressed MOVie container atom + case 'rmra': // Reference Movie Record Atom + case 'rmda': // Reference Movie Descriptor Atom + case 'gmhd': // Generic Media info HeaDer atom (seen on QTVR) + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); + break; + + case 'ilst': // Item LiST container atom + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); + + // some "ilst" atoms contain data atoms that have a numeric name, and the data is far more accessible if the returned array is compacted + $allnumericnames = true; + foreach ($atom_structure['subatoms'] as $subatomarray) { + if (!is_integer($subatomarray['name']) || (count($subatomarray['subatoms']) != 1)) { + $allnumericnames = false; + break; + } + } + if ($allnumericnames) { + $newData = array(); + foreach ($atom_structure['subatoms'] as $subatomarray) { + foreach ($subatomarray['subatoms'] as $newData_subatomarray) { + unset($newData_subatomarray['hierarchy'], $newData_subatomarray['name']); + $newData[$subatomarray['name']] = $newData_subatomarray; + break; + } + } + $atom_structure['data'] = $newData; + unset($atom_structure['subatoms']); + } + break; + + case "\x00\x00\x00\x01": + case "\x00\x00\x00\x02": + case "\x00\x00\x00\x03": + case "\x00\x00\x00\x04": + case "\x00\x00\x00\x05": + $atomname = getid3_lib::BigEndian2Int($atomname); + $atom_structure['name'] = $atomname; + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); + break; + + case 'stbl': // Sample TaBLe container atom + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); + $isVideo = false; + $framerate = 0; + $framecount = 0; + foreach ($atom_structure['subatoms'] as $key => $value_array) { + if (isset($value_array['sample_description_table'])) { + foreach ($value_array['sample_description_table'] as $key2 => $value_array2) { + if (isset($value_array2['data_format'])) { + switch ($value_array2['data_format']) { + case 'avc1': + case 'mp4v': + // video data + $isVideo = true; + break; + case 'mp4a': + // audio data + break; + } + } + } + } elseif (isset($value_array['time_to_sample_table'])) { + foreach ($value_array['time_to_sample_table'] as $key2 => $value_array2) { + if (isset($value_array2['sample_count']) && isset($value_array2['sample_duration']) && ($value_array2['sample_duration'] > 0)) { + $framerate = round($info['quicktime']['time_scale'] / $value_array2['sample_duration'], 3); + $framecount = $value_array2['sample_count']; + } + } + } + } + if ($isVideo && $framerate) { + $info['quicktime']['video']['frame_rate'] = $framerate; + $info['video']['frame_rate'] = $info['quicktime']['video']['frame_rate']; + } + if ($isVideo && $framecount) { + $info['quicktime']['video']['frame_count'] = $framecount; + } + break; + + + case 'aART': // Album ARTist + case 'catg': // CaTeGory + case 'covr': // COVeR artwork + case 'cpil': // ComPILation + case 'cprt': // CoPyRighT + case 'desc': // DESCription + case 'disk': // DISK number + case 'egid': // Episode Global ID + case 'gnre': // GeNRE + case 'keyw': // KEYWord + case 'ldes': + case 'pcst': // PodCaST + case 'pgap': // GAPless Playback + case 'purd': // PURchase Date + case 'purl': // Podcast URL + case 'rati': + case 'rndu': + case 'rpdu': + case 'rtng': // RaTiNG + case 'stik': + case 'tmpo': // TeMPO (BPM) + case 'trkn': // TRacK Number + case 'tves': // TV EpiSode + case 'tvnn': // TV Network Name + case 'tvsh': // TV SHow Name + case 'tvsn': // TV SeasoN + case 'akID': // iTunes store account type + case 'apID': + case 'atID': + case 'cmID': + case 'cnID': + case 'geID': + case 'plID': + case 'sfID': // iTunes store country + case '©alb': // ALBum + case '©art': // ARTist + case '©ART': + case '©aut': + case '©cmt': // CoMmenT + case '©com': // COMposer + case '©cpy': + case '©day': // content created year + case '©dir': + case '©ed1': + case '©ed2': + case '©ed3': + case '©ed4': + case '©ed5': + case '©ed6': + case '©ed7': + case '©ed8': + case '©ed9': + case '©enc': + case '©fmt': + case '©gen': // GENre + case '©grp': // GRouPing + case '©hst': + case '©inf': + case '©lyr': // LYRics + case '©mak': + case '©mod': + case '©nam': // full NAMe + case '©ope': + case '©PRD': + case '©prd': + case '©prf': + case '©req': + case '©src': + case '©swr': + case '©too': // encoder + case '©trk': // TRacK + case '©url': + case '©wrn': + case '©wrt': // WRiTer + case '----': // itunes specific + if ($atom_parent == 'udta') { + // User data atom handler + $atom_structure['data_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); + $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); + $atom_structure['data'] = substr($atom_data, 4); + + $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); + if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) { + $info['comments']['language'][] = $atom_structure['language']; + } + } else { + // Apple item list box atom handler + $atomoffset = 0; + if (substr($atom_data, 2, 2) == "\x10\xB5") { + // not sure what it means, but observed on iPhone4 data. + // Each $atom_data has 2 bytes of datasize, plus 0x10B5, then data + while ($atomoffset < strlen($atom_data)) { + $boxsmallsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 2)); + $boxsmalltype = substr($atom_data, $atomoffset + 2, 2); + $boxsmalldata = substr($atom_data, $atomoffset + 4, $boxsmallsize); + switch ($boxsmalltype) { + case "\x10\xB5": + $atom_structure['data'] = $boxsmalldata; + break; + default: + $info['warning'][] = 'Unknown QuickTime smallbox type: "'.getid3_lib::PrintHexBytes($boxsmalltype).'" at offset '.$baseoffset; + $atom_structure['data'] = $atom_data; + break; + } + $atomoffset += (4 + $boxsmallsize); + } + } else { + while ($atomoffset < strlen($atom_data)) { + $boxsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 4)); + $boxtype = substr($atom_data, $atomoffset + 4, 4); + $boxdata = substr($atom_data, $atomoffset + 8, $boxsize - 8); + + switch ($boxtype) { + case 'mean': + case 'name': + $atom_structure[$boxtype] = substr($boxdata, 4); + break; + + case 'data': + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($boxdata, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($boxdata, 1, 3)); + switch ($atom_structure['flags_raw']) { + case 0: // data flag + case 21: // tmpo/cpil flag + switch ($atomname) { + case 'cpil': + case 'pcst': + case 'pgap': + $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); + break; + + case 'tmpo': + $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 2)); + break; + + case 'disk': + case 'trkn': + $num = getid3_lib::BigEndian2Int(substr($boxdata, 10, 2)); + $num_total = getid3_lib::BigEndian2Int(substr($boxdata, 12, 2)); + $atom_structure['data'] = empty($num) ? '' : $num; + $atom_structure['data'] .= empty($num_total) ? '' : '/'.$num_total; + break; + + case 'gnre': + $GenreID = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); + $atom_structure['data'] = getid3_id3v1::LookupGenreName($GenreID - 1); + break; + + case 'rtng': + $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); + $atom_structure['data'] = $this->QuicktimeContentRatingLookup($atom_structure[$atomname]); + break; + + case 'stik': + $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); + $atom_structure['data'] = $this->QuicktimeSTIKLookup($atom_structure[$atomname]); + break; + + case 'sfID': + $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); + $atom_structure['data'] = $this->QuicktimeStoreFrontCodeLookup($atom_structure[$atomname]); + break; + + case 'egid': + case 'purl': + $atom_structure['data'] = substr($boxdata, 8); + break; + + default: + $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); + } + break; + + case 1: // text flag + case 13: // image flag + default: + $atom_structure['data'] = substr($boxdata, 8); + break; + + } + break; + + default: + $info['warning'][] = 'Unknown QuickTime box type: "'.getid3_lib::PrintHexBytes($boxtype).'" at offset '.$baseoffset; + $atom_structure['data'] = $atom_data; + + } + $atomoffset += $boxsize; + } + } + } + $this->CopyToAppropriateCommentsSection($atomname, $atom_structure['data'], $atom_structure['name']); + break; + + + case 'play': // auto-PLAY atom + $atom_structure['autoplay'] = (bool) getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + + $info['quicktime']['autoplay'] = $atom_structure['autoplay']; + break; + + + case 'WLOC': // Window LOCation atom + $atom_structure['location_x'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); + $atom_structure['location_y'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); + break; + + + case 'LOOP': // LOOPing atom + case 'SelO': // play SELection Only atom + case 'AllF': // play ALL Frames atom + $atom_structure['data'] = getid3_lib::BigEndian2Int($atom_data); + break; + + + case 'name': // + case 'MCPS': // Media Cleaner PRo + case '@PRM': // adobe PReMiere version + case '@PRQ': // adobe PRemiere Quicktime version + $atom_structure['data'] = $atom_data; + break; + + + case 'cmvd': // Compressed MooV Data atom + // Code by ubergeekØubergeek*tv based on information from + // http://developer.apple.com/quicktime/icefloe/dispatch012.html + $atom_structure['unCompressedSize'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); + + $CompressedFileData = substr($atom_data, 4); + if ($UncompressedHeader = @gzuncompress($CompressedFileData)) { + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($UncompressedHeader, 0, $atomHierarchy, $ParseAllPossibleAtoms); + } else { + $info['warning'][] = 'Error decompressing compressed MOV atom at offset '.$atom_structure['offset']; + } + break; + + + case 'dcom': // Data COMpression atom + $atom_structure['compression_id'] = $atom_data; + $atom_structure['compression_text'] = $this->QuicktimeDCOMLookup($atom_data); + break; + + + case 'rdrf': // Reference movie Data ReFerence atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); + $atom_structure['flags']['internal_data'] = (bool) ($atom_structure['flags_raw'] & 0x000001); + + $atom_structure['reference_type_name'] = substr($atom_data, 4, 4); + $atom_structure['reference_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + switch ($atom_structure['reference_type_name']) { + case 'url ': + $atom_structure['url'] = $this->NoNullString(substr($atom_data, 12)); + break; + + case 'alis': + $atom_structure['file_alias'] = substr($atom_data, 12); + break; + + case 'rsrc': + $atom_structure['resource_alias'] = substr($atom_data, 12); + break; + + default: + $atom_structure['data'] = substr($atom_data, 12); + break; + } + break; + + + case 'rmqu': // Reference Movie QUality atom + $atom_structure['movie_quality'] = getid3_lib::BigEndian2Int($atom_data); + break; + + + case 'rmcs': // Reference Movie Cpu Speed atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['cpu_speed_rating'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); + break; + + + case 'rmvc': // Reference Movie Version Check atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['gestalt_selector'] = substr($atom_data, 4, 4); + $atom_structure['gestalt_value_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + $atom_structure['gestalt_value'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); + $atom_structure['gestalt_check_type'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2)); + break; + + + case 'rmcd': // Reference Movie Component check atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['component_type'] = substr($atom_data, 4, 4); + $atom_structure['component_subtype'] = substr($atom_data, 8, 4); + $atom_structure['component_manufacturer'] = substr($atom_data, 12, 4); + $atom_structure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); + $atom_structure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); + $atom_structure['component_min_version'] = getid3_lib::BigEndian2Int(substr($atom_data, 24, 4)); + break; + + + case 'rmdr': // Reference Movie Data Rate atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['data_rate'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + + $atom_structure['data_rate_bps'] = $atom_structure['data_rate'] * 10; + break; + + + case 'rmla': // Reference Movie Language Atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); + + $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); + if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) { + $info['comments']['language'][] = $atom_structure['language']; + } + break; + + + case 'rmla': // Reference Movie Language Atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); + break; + + + case 'ptv ': // Print To Video - defines a movie's full screen mode + // http://developer.apple.com/documentation/QuickTime/APIREF/SOURCESIV/at_ptv-_pg.htm + $atom_structure['display_size_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); + $atom_structure['reserved_1'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); // hardcoded: 0x0000 + $atom_structure['reserved_2'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x0000 + $atom_structure['slide_show_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 1)); + $atom_structure['play_on_open_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 7, 1)); + + $atom_structure['flags']['play_on_open'] = (bool) $atom_structure['play_on_open_flag']; + $atom_structure['flags']['slide_show'] = (bool) $atom_structure['slide_show_flag']; + + $ptv_lookup[0] = 'normal'; + $ptv_lookup[1] = 'double'; + $ptv_lookup[2] = 'half'; + $ptv_lookup[3] = 'full'; + $ptv_lookup[4] = 'current'; + if (isset($ptv_lookup[$atom_structure['display_size_raw']])) { + $atom_structure['display_size'] = $ptv_lookup[$atom_structure['display_size_raw']]; + } else { + $info['warning'][] = 'unknown "ptv " display constant ('.$atom_structure['display_size_raw'].')'; + } + break; + + + case 'stsd': // Sample Table Sample Description atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $stsdEntriesDataOffset = 8; + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['sample_description_table'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 4)); + $stsdEntriesDataOffset += 4; + $atom_structure['sample_description_table'][$i]['data_format'] = substr($atom_data, $stsdEntriesDataOffset, 4); + $stsdEntriesDataOffset += 4; + $atom_structure['sample_description_table'][$i]['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 6)); + $stsdEntriesDataOffset += 6; + $atom_structure['sample_description_table'][$i]['reference_index'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 2)); + $stsdEntriesDataOffset += 2; + $atom_structure['sample_description_table'][$i]['data'] = substr($atom_data, $stsdEntriesDataOffset, ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2)); + $stsdEntriesDataOffset += ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2); + + $atom_structure['sample_description_table'][$i]['encoder_version'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 0, 2)); + $atom_structure['sample_description_table'][$i]['encoder_revision'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 2, 2)); + $atom_structure['sample_description_table'][$i]['encoder_vendor'] = substr($atom_structure['sample_description_table'][$i]['data'], 4, 4); + + switch ($atom_structure['sample_description_table'][$i]['encoder_vendor']) { + + case "\x00\x00\x00\x00": + // audio atom + $atom_structure['sample_description_table'][$i]['audio_channels'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 2)); + $atom_structure['sample_description_table'][$i]['audio_bit_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 10, 2)); + $atom_structure['sample_description_table'][$i]['audio_compression_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 2)); + $atom_structure['sample_description_table'][$i]['audio_packet_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 14, 2)); + $atom_structure['sample_description_table'][$i]['audio_sample_rate'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 16, 4)); + + switch ($atom_structure['sample_description_table'][$i]['data_format']) { + case 'avc1': + case 'mp4v': + $info['fileformat'] = 'mp4'; + $info['video']['fourcc'] = $atom_structure['sample_description_table'][$i]['data_format']; + //$info['warning'][] = 'This version of getID3() ['.$this->getid3->version().'] does not fully support MPEG-4 audio/video streams'; // 2011-02-18: why am I warning about this again? What's not supported? + break; + + case 'qtvr': + $info['video']['dataformat'] = 'quicktimevr'; + break; + + case 'mp4a': + default: + $info['quicktime']['audio']['codec'] = $this->QuicktimeAudioCodecLookup($atom_structure['sample_description_table'][$i]['data_format']); + $info['quicktime']['audio']['sample_rate'] = $atom_structure['sample_description_table'][$i]['audio_sample_rate']; + $info['quicktime']['audio']['channels'] = $atom_structure['sample_description_table'][$i]['audio_channels']; + $info['quicktime']['audio']['bit_depth'] = $atom_structure['sample_description_table'][$i]['audio_bit_depth']; + $info['audio']['codec'] = $info['quicktime']['audio']['codec']; + $info['audio']['sample_rate'] = $info['quicktime']['audio']['sample_rate']; + $info['audio']['channels'] = $info['quicktime']['audio']['channels']; + $info['audio']['bits_per_sample'] = $info['quicktime']['audio']['bit_depth']; + switch ($atom_structure['sample_description_table'][$i]['data_format']) { + case 'raw ': // PCM + case 'alac': // Apple Lossless Audio Codec + $info['audio']['lossless'] = true; + break; + default: + $info['audio']['lossless'] = false; + break; + } + break; + } + break; + + default: + switch ($atom_structure['sample_description_table'][$i]['data_format']) { + case 'mp4s': + $info['fileformat'] = 'mp4'; + break; + + default: + // video atom + $atom_structure['sample_description_table'][$i]['video_temporal_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 4)); + $atom_structure['sample_description_table'][$i]['video_spatial_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 4)); + $atom_structure['sample_description_table'][$i]['video_frame_width'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 16, 2)); + $atom_structure['sample_description_table'][$i]['video_frame_height'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 18, 2)); + $atom_structure['sample_description_table'][$i]['video_resolution_x'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 20, 4)); + $atom_structure['sample_description_table'][$i]['video_resolution_y'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 24, 4)); + $atom_structure['sample_description_table'][$i]['video_data_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 28, 4)); + $atom_structure['sample_description_table'][$i]['video_frame_count'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 32, 2)); + $atom_structure['sample_description_table'][$i]['video_encoder_name_len'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 34, 1)); + $atom_structure['sample_description_table'][$i]['video_encoder_name'] = substr($atom_structure['sample_description_table'][$i]['data'], 35, $atom_structure['sample_description_table'][$i]['video_encoder_name_len']); + $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 66, 2)); + $atom_structure['sample_description_table'][$i]['video_color_table_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 68, 2)); + + $atom_structure['sample_description_table'][$i]['video_pixel_color_type'] = (($atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] > 32) ? 'grayscale' : 'color'); + $atom_structure['sample_description_table'][$i]['video_pixel_color_name'] = $this->QuicktimeColorNameLookup($atom_structure['sample_description_table'][$i]['video_pixel_color_depth']); + + if ($atom_structure['sample_description_table'][$i]['video_pixel_color_name'] != 'invalid') { + $info['quicktime']['video']['codec_fourcc'] = $atom_structure['sample_description_table'][$i]['data_format']; + $info['quicktime']['video']['codec_fourcc_lookup'] = $this->QuicktimeVideoCodecLookup($atom_structure['sample_description_table'][$i]['data_format']); + $info['quicktime']['video']['codec'] = (($atom_structure['sample_description_table'][$i]['video_encoder_name_len'] > 0) ? $atom_structure['sample_description_table'][$i]['video_encoder_name'] : $atom_structure['sample_description_table'][$i]['data_format']); + $info['quicktime']['video']['color_depth'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_depth']; + $info['quicktime']['video']['color_depth_name'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_name']; + + $info['video']['codec'] = $info['quicktime']['video']['codec']; + $info['video']['bits_per_sample'] = $info['quicktime']['video']['color_depth']; + } + $info['video']['lossless'] = false; + $info['video']['pixel_aspect_ratio'] = (float) 1; + break; + } + break; + } + switch (strtolower($atom_structure['sample_description_table'][$i]['data_format'])) { + case 'mp4a': + $info['audio']['dataformat'] = 'mp4'; + $info['quicktime']['audio']['codec'] = 'mp4'; + break; + + case '3ivx': + case '3iv1': + case '3iv2': + $info['video']['dataformat'] = '3ivx'; + break; + + case 'xvid': + $info['video']['dataformat'] = 'xvid'; + break; + + case 'mp4v': + $info['video']['dataformat'] = 'mpeg4'; + break; + + case 'divx': + case 'div1': + case 'div2': + case 'div3': + case 'div4': + case 'div5': + case 'div6': + $info['video']['dataformat'] = 'divx'; + break; + + default: + // do nothing + break; + } + unset($atom_structure['sample_description_table'][$i]['data']); + } + break; + + + case 'stts': // Sample Table Time-to-Sample atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $sttsEntriesDataOffset = 8; + //$FrameRateCalculatorArray = array(); + $frames_count = 0; + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['time_to_sample_table'][$i]['sample_count'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4)); + $sttsEntriesDataOffset += 4; + $atom_structure['time_to_sample_table'][$i]['sample_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4)); + $sttsEntriesDataOffset += 4; + + $frames_count += $atom_structure['time_to_sample_table'][$i]['sample_count']; + + // THIS SECTION REPLACED WITH CODE IN "stbl" ATOM + //if (!empty($info['quicktime']['time_scale']) && ($atom_structure['time_to_sample_table'][$i]['sample_duration'] > 0)) { + // $stts_new_framerate = $info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration']; + // if ($stts_new_framerate <= 60) { + // // some atoms have durations of "1" giving a very large framerate, which probably is not right + // $info['video']['frame_rate'] = max($info['video']['frame_rate'], $stts_new_framerate); + // } + //} + // + //$FrameRateCalculatorArray[($info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration'])] += $atom_structure['time_to_sample_table'][$i]['sample_count']; + } + $info['quicktime']['stts_framecount'][] = $frames_count; + //$sttsFramesTotal = 0; + //$sttsSecondsTotal = 0; + //foreach ($FrameRateCalculatorArray as $frames_per_second => $frame_count) { + // if (($frames_per_second > 60) || ($frames_per_second < 1)) { + // // not video FPS information, probably audio information + // $sttsFramesTotal = 0; + // $sttsSecondsTotal = 0; + // break; + // } + // $sttsFramesTotal += $frame_count; + // $sttsSecondsTotal += $frame_count / $frames_per_second; + //} + //if (($sttsFramesTotal > 0) && ($sttsSecondsTotal > 0)) { + // if (($sttsFramesTotal / $sttsSecondsTotal) > $info['video']['frame_rate']) { + // $info['video']['frame_rate'] = $sttsFramesTotal / $sttsSecondsTotal; + // } + //} + break; + + + case 'stss': // Sample Table Sync Sample (key frames) atom + if ($ParseAllPossibleAtoms) { + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $stssEntriesDataOffset = 8; + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['time_to_sample_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stssEntriesDataOffset, 4)); + $stssEntriesDataOffset += 4; + } + } + break; + + + case 'stsc': // Sample Table Sample-to-Chunk atom + if ($ParseAllPossibleAtoms) { + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $stscEntriesDataOffset = 8; + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['sample_to_chunk_table'][$i]['first_chunk'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); + $stscEntriesDataOffset += 4; + $atom_structure['sample_to_chunk_table'][$i]['samples_per_chunk'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); + $stscEntriesDataOffset += 4; + $atom_structure['sample_to_chunk_table'][$i]['sample_description'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); + $stscEntriesDataOffset += 4; + } + } + break; + + + case 'stsz': // Sample Table SiZe atom + if ($ParseAllPossibleAtoms) { + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['sample_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + $stszEntriesDataOffset = 12; + if ($atom_structure['sample_size'] == 0) { + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['sample_size_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stszEntriesDataOffset, 4)); + $stszEntriesDataOffset += 4; + } + } + } + break; + + + case 'stco': // Sample Table Chunk Offset atom + if ($ParseAllPossibleAtoms) { + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $stcoEntriesDataOffset = 8; + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 4)); + $stcoEntriesDataOffset += 4; + } + } + break; + + + case 'co64': // Chunk Offset 64-bit (version of "stco" that supports > 2GB files) + if ($ParseAllPossibleAtoms) { + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $stcoEntriesDataOffset = 8; + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 8)); + $stcoEntriesDataOffset += 8; + } + } + break; + + + case 'dref': // Data REFerence atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $drefDataOffset = 8; + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['data_references'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 4)); + $drefDataOffset += 4; + $atom_structure['data_references'][$i]['type'] = substr($atom_data, $drefDataOffset, 4); + $drefDataOffset += 4; + $atom_structure['data_references'][$i]['version'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 1)); + $drefDataOffset += 1; + $atom_structure['data_references'][$i]['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 3)); // hardcoded: 0x0000 + $drefDataOffset += 3; + $atom_structure['data_references'][$i]['data'] = substr($atom_data, $drefDataOffset, ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3)); + $drefDataOffset += ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3); + + $atom_structure['data_references'][$i]['flags']['self_reference'] = (bool) ($atom_structure['data_references'][$i]['flags_raw'] & 0x001); + } + break; + + + case 'gmin': // base Media INformation atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); + $atom_structure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); + $atom_structure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 2)); + $atom_structure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); + $atom_structure['balance'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 2)); + $atom_structure['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2)); + break; + + + case 'smhd': // Sound Media information HeaDer atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['balance'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); + $atom_structure['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); + break; + + + case 'vmhd': // Video Media information HeaDer atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); + $atom_structure['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); + $atom_structure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); + $atom_structure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 2)); + $atom_structure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); + + $atom_structure['flags']['no_lean_ahead'] = (bool) ($atom_structure['flags_raw'] & 0x001); + break; + + + case 'hdlr': // HanDLeR reference atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['component_type'] = substr($atom_data, 4, 4); + $atom_structure['component_subtype'] = substr($atom_data, 8, 4); + $atom_structure['component_manufacturer'] = substr($atom_data, 12, 4); + $atom_structure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); + $atom_structure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); + $atom_structure['component_name'] = $this->Pascal2String(substr($atom_data, 24)); + + if (($atom_structure['component_subtype'] == 'STpn') && ($atom_structure['component_manufacturer'] == 'zzzz')) { + $info['video']['dataformat'] = 'quicktimevr'; + } + break; + + + case 'mdhd': // MeDia HeaDer atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + $atom_structure['time_scale'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); + $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); + $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 2)); + $atom_structure['quality'] = getid3_lib::BigEndian2Int(substr($atom_data, 22, 2)); + + if ($atom_structure['time_scale'] == 0) { + $info['error'][] = 'Corrupt Quicktime file: mdhd.time_scale == zero'; + return false; + } + $info['quicktime']['time_scale'] = (isset($info['quicktime']['time_scale']) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']); + + $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); + $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); + $atom_structure['playtime_seconds'] = $atom_structure['duration'] / $atom_structure['time_scale']; + $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); + if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) { + $info['comments']['language'][] = $atom_structure['language']; + } + break; + + + case 'pnot': // Preview atom + $atom_structure['modification_date'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); // "standard Macintosh format" + $atom_structure['version_number'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x00 + $atom_structure['atom_type'] = substr($atom_data, 6, 4); // usually: 'PICT' + $atom_structure['atom_index'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); // usually: 0x01 + + $atom_structure['modification_date_unix'] = getid3_lib::DateMac2Unix($atom_structure['modification_date']); + break; + + + case 'crgn': // Clipping ReGioN atom + $atom_structure['region_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); // The Region size, Region boundary box, + $atom_structure['boundary_box'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 8)); // and Clipping region data fields + $atom_structure['clipping_data'] = substr($atom_data, 10); // constitute a QuickDraw region. + break; + + + case 'load': // track LOAD settings atom + $atom_structure['preload_start_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); + $atom_structure['preload_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $atom_structure['preload_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + $atom_structure['default_hints_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); + + $atom_structure['default_hints']['double_buffer'] = (bool) ($atom_structure['default_hints_raw'] & 0x0020); + $atom_structure['default_hints']['high_quality'] = (bool) ($atom_structure['default_hints_raw'] & 0x0100); + break; + + + case 'tmcd': // TiMe CoDe atom + case 'chap': // CHAPter list atom + case 'sync': // SYNChronization atom + case 'scpt': // tranSCriPT atom + case 'ssrc': // non-primary SouRCe atom + for ($i = 0; $i < (strlen($atom_data) % 4); $i++) { + $atom_structure['track_id'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $i * 4, 4)); + } + break; + + + case 'elst': // Edit LiST atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + for ($i = 0; $i < $atom_structure['number_entries']; $i++ ) { + $atom_structure['edit_list'][$i]['track_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 0, 4)); + $atom_structure['edit_list'][$i]['media_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 4, 4)); + $atom_structure['edit_list'][$i]['media_rate'] = getid3_lib::FixedPoint16_16(substr($atom_data, 8 + ($i * 12) + 8, 4)); + } + break; + + + case 'kmat': // compressed MATte atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['matte_data_raw'] = substr($atom_data, 4); + break; + + + case 'ctab': // Color TABle atom + $atom_structure['color_table_seed'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); // hardcoded: 0x00000000 + $atom_structure['color_table_flags'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x8000 + $atom_structure['color_table_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)) + 1; + for ($colortableentry = 0; $colortableentry < $atom_structure['color_table_size']; $colortableentry++) { + $atom_structure['color_table'][$colortableentry]['alpha'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 0, 2)); + $atom_structure['color_table'][$colortableentry]['red'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 2, 2)); + $atom_structure['color_table'][$colortableentry]['green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 4, 2)); + $atom_structure['color_table'][$colortableentry]['blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 6, 2)); + } + break; + + + case 'mvhd': // MoVie HeaDer atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); + $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + $atom_structure['time_scale'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); + $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); + $atom_structure['preferred_rate'] = getid3_lib::FixedPoint16_16(substr($atom_data, 20, 4)); + $atom_structure['preferred_volume'] = getid3_lib::FixedPoint8_8(substr($atom_data, 24, 2)); + $atom_structure['reserved'] = substr($atom_data, 26, 10); + $atom_structure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atom_data, 36, 4)); + $atom_structure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4)); + $atom_structure['matrix_u'] = getid3_lib::FixedPoint2_30(substr($atom_data, 44, 4)); + $atom_structure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atom_data, 48, 4)); + $atom_structure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4)); + $atom_structure['matrix_v'] = getid3_lib::FixedPoint2_30(substr($atom_data, 56, 4)); + $atom_structure['matrix_x'] = getid3_lib::FixedPoint16_16(substr($atom_data, 60, 4)); + $atom_structure['matrix_y'] = getid3_lib::FixedPoint16_16(substr($atom_data, 64, 4)); + $atom_structure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atom_data, 68, 4)); + $atom_structure['preview_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 72, 4)); + $atom_structure['preview_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 76, 4)); + $atom_structure['poster_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 80, 4)); + $atom_structure['selection_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 84, 4)); + $atom_structure['selection_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 88, 4)); + $atom_structure['current_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 92, 4)); + $atom_structure['next_track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 96, 4)); + + if ($atom_structure['time_scale'] == 0) { + $info['error'][] = 'Corrupt Quicktime file: mvhd.time_scale == zero'; + return false; + } + $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); + $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); + $info['quicktime']['time_scale'] = (isset($info['quicktime']['time_scale']) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']); + $info['quicktime']['display_scale'] = $atom_structure['matrix_a']; + $info['playtime_seconds'] = $atom_structure['duration'] / $atom_structure['time_scale']; + break; + + + case 'tkhd': // TracK HeaDer atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); + $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + $atom_structure['trackid'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); + $atom_structure['reserved1'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); + $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); + $atom_structure['reserved2'] = getid3_lib::BigEndian2Int(substr($atom_data, 24, 8)); + $atom_structure['layer'] = getid3_lib::BigEndian2Int(substr($atom_data, 32, 2)); + $atom_structure['alternate_group'] = getid3_lib::BigEndian2Int(substr($atom_data, 34, 2)); + $atom_structure['volume'] = getid3_lib::FixedPoint8_8(substr($atom_data, 36, 2)); + $atom_structure['reserved3'] = getid3_lib::BigEndian2Int(substr($atom_data, 38, 2)); + $atom_structure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4)); + $atom_structure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atom_data, 44, 4)); + $atom_structure['matrix_u'] = getid3_lib::FixedPoint16_16(substr($atom_data, 48, 4)); + $atom_structure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4)); + $atom_structure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atom_data, 56, 4)); + $atom_structure['matrix_v'] = getid3_lib::FixedPoint16_16(substr($atom_data, 60, 4)); + $atom_structure['matrix_x'] = getid3_lib::FixedPoint2_30(substr($atom_data, 64, 4)); + $atom_structure['matrix_y'] = getid3_lib::FixedPoint2_30(substr($atom_data, 68, 4)); + $atom_structure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atom_data, 72, 4)); + $atom_structure['width'] = getid3_lib::FixedPoint16_16(substr($atom_data, 76, 4)); + $atom_structure['height'] = getid3_lib::FixedPoint16_16(substr($atom_data, 80, 4)); + + $atom_structure['flags']['enabled'] = (bool) ($atom_structure['flags_raw'] & 0x0001); + $atom_structure['flags']['in_movie'] = (bool) ($atom_structure['flags_raw'] & 0x0002); + $atom_structure['flags']['in_preview'] = (bool) ($atom_structure['flags_raw'] & 0x0004); + $atom_structure['flags']['in_poster'] = (bool) ($atom_structure['flags_raw'] & 0x0008); + $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); + $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); + + if ($atom_structure['flags']['enabled'] == 1) { + if (!isset($info['video']['resolution_x']) || !isset($info['video']['resolution_y'])) { + $info['video']['resolution_x'] = $atom_structure['width']; + $info['video']['resolution_y'] = $atom_structure['height']; + } + $info['video']['resolution_x'] = max($info['video']['resolution_x'], $atom_structure['width']); + $info['video']['resolution_y'] = max($info['video']['resolution_y'], $atom_structure['height']); + $info['quicktime']['video']['resolution_x'] = $info['video']['resolution_x']; + $info['quicktime']['video']['resolution_y'] = $info['video']['resolution_y']; + } else { + if (isset($info['video']['resolution_x'])) { unset($info['video']['resolution_x']); } + if (isset($info['video']['resolution_y'])) { unset($info['video']['resolution_y']); } + if (isset($info['quicktime']['video'])) { unset($info['quicktime']['video']); } + } + break; + + + case 'iods': // Initial Object DeScriptor atom + // http://www.koders.com/c/fid1FAB3E762903DC482D8A246D4A4BF9F28E049594.aspx?s=windows.h + // http://libquicktime.sourcearchive.com/documentation/1.0.2plus-pdebian/iods_8c-source.html + $offset = 0; + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 3)); + $offset += 3; + $atom_structure['mp4_iod_tag'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['length'] = $this->quicktime_read_mp4_descr_length($atom_data, $offset); + //$offset already adjusted by quicktime_read_mp4_descr_length() + $atom_structure['object_descriptor_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); + $offset += 2; + $atom_structure['od_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['scene_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['audio_profile_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['video_profile_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['graphics_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + + $atom_structure['num_iods_tracks'] = ($atom_structure['length'] - 7) / 6; // 6 bytes would only be right if all tracks use 1-byte length fields + for ($i = 0; $i < $atom_structure['num_iods_tracks']; $i++) { + $atom_structure['track'][$i]['ES_ID_IncTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['track'][$i]['length'] = $this->quicktime_read_mp4_descr_length($atom_data, $offset); + //$offset already adjusted by quicktime_read_mp4_descr_length() + $atom_structure['track'][$i]['track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 4)); + $offset += 4; + } + + $atom_structure['audio_profile_name'] = $this->QuicktimeIODSaudioProfileName($atom_structure['audio_profile_id']); + $atom_structure['video_profile_name'] = $this->QuicktimeIODSvideoProfileName($atom_structure['video_profile_id']); + break; + + case 'ftyp': // FileTYPe (?) atom (for MP4 it seems) + $atom_structure['signature'] = substr($atom_data, 0, 4); + $atom_structure['unknown_1'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $atom_structure['fourcc'] = substr($atom_data, 8, 4); + break; + + case 'mdat': // Media DATa atom + case 'free': // FREE space atom + case 'skip': // SKIP atom + case 'wide': // 64-bit expansion placeholder atom + // 'mdat' data is too big to deal with, contains no useful metadata + // 'free', 'skip' and 'wide' are just padding, contains no useful data at all + + // When writing QuickTime files, it is sometimes necessary to update an atom's size. + // It is impossible to update a 32-bit atom to a 64-bit atom since the 32-bit atom + // is only 8 bytes in size, and the 64-bit atom requires 16 bytes. Therefore, QuickTime + // puts an 8-byte placeholder atom before any atoms it may have to update the size of. + // In this way, if the atom needs to be converted from a 32-bit to a 64-bit atom, the + // placeholder atom can be overwritten to obtain the necessary 8 extra bytes. + // The placeholder atom has a type of kWideAtomPlaceholderType ( 'wide' ). + break; + + + case 'nsav': // NoSAVe atom + // http://developer.apple.com/technotes/tn/tn2038.html + $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); + break; + + case 'ctyp': // Controller TYPe atom (seen on QTVR) + // http://homepages.slingshot.co.nz/~helmboy/quicktime/formats/qtm-layout.txt + // some controller names are: + // 0x00 + 'std' for linear movie + // 'none' for no controls + $atom_structure['ctyp'] = substr($atom_data, 0, 4); + $info['quicktime']['controller'] = $atom_structure['ctyp']; + switch ($atom_structure['ctyp']) { + case 'qtvr': + $info['video']['dataformat'] = 'quicktimevr'; + break; + } + break; + + case 'pano': // PANOrama track (seen on QTVR) + $atom_structure['pano'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); + break; + + case 'hint': // HINT track + case 'hinf': // + case 'hinv': // + case 'hnti': // + $info['quicktime']['hinting'] = true; + break; + + case 'imgt': // IMaGe Track reference (kQTVRImageTrackRefType) (seen on QTVR) + for ($i = 0; $i < ($atom_structure['size'] - 8); $i += 4) { + $atom_structure['imgt'][] = getid3_lib::BigEndian2Int(substr($atom_data, $i, 4)); + } + break; + + + // Observed-but-not-handled atom types are just listed here to prevent warnings being generated + case 'FXTC': // Something to do with Adobe After Effects (?) + case 'PrmA': + case 'code': + case 'FIEL': // this is NOT "fiel" (Field Ordering) as describe here: http://developer.apple.com/documentation/QuickTime/QTFF/QTFFChap3/chapter_4_section_2.html + case 'tapt': // TrackApertureModeDimensionsAID - http://developer.apple.com/documentation/QuickTime/Reference/QT7-1_Update_Reference/Constants/Constants.html + // tapt seems to be used to compute the video size [http://www.getid3.org/phpBB3/viewtopic.php?t=838] + // * http://lists.apple.com/archives/quicktime-api/2006/Aug/msg00014.html + // * http://handbrake.fr/irclogs/handbrake-dev/handbrake-dev20080128_pg2.html + case 'ctts':// STCompositionOffsetAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html + case 'cslg':// STCompositionShiftLeastGreatestAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html + case 'sdtp':// STSampleDependencyAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html + case 'stps':// STPartialSyncSampleAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html + //$atom_structure['data'] = $atom_data; + break; + + case '©xyz': // GPS latitude+longitude+altitude + $atom_structure['data'] = $atom_data; + if (preg_match('#([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)?/$#i', $atom_data, $matches)) { + @list($all, $latitude, $longitude, $altitude) = $matches; + $info['quicktime']['comments']['gps_latitude'][] = floatval($latitude); + $info['quicktime']['comments']['gps_longitude'][] = floatval($longitude); + if (!empty($altitude)) { + $info['quicktime']['comments']['gps_altitude'][] = floatval($altitude); + } + } else { + $info['warning'][] = 'QuickTime atom "©xyz" data does not match expected data pattern at offset '.$baseoffset.'. Please report as getID3() bug.'; + } + break; + + case 'NCDT': + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html + // Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100 + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 4, $atomHierarchy, $ParseAllPossibleAtoms); + break; + case 'NCTH': // Nikon Camera THumbnail image + case 'NCVW': // Nikon Camera preVieW image + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html + if (preg_match('/^\xFF\xD8\xFF/', $atom_data)) { + $atom_structure['data'] = $atom_data; + $atom_structure['image_mime'] = 'image/jpeg'; + $atom_structure['description'] = (($atomname == 'NCTH') ? 'Nikon Camera Thumbnail Image' : (($atomname == 'NCVW') ? 'Nikon Camera Preview Image' : 'Nikon preview image')); + $info['quicktime']['comments']['picture'][] = array('image_mime'=>$atom_structure['image_mime'], 'data'=>$atom_data, 'description'=>$atom_structure['description']); + } + break; + case 'NCHD': // MakerNoteVersion + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html + $atom_structure['data'] = $atom_data; + break; + case 'NCTG': // NikonTags + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG + $atom_structure['data'] = $this->QuicktimeParseNikonNCTG($atom_data); + break; + case 'NCDB': // NikonTags + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html + $atom_structure['data'] = $atom_data; + break; + + case "\x00\x00\x00\x00": + case 'meta': // METAdata atom + // some kind of metacontainer, may contain a big data dump such as: + // mdta keys  mdtacom.apple.quicktime.make (mdtacom.apple.quicktime.creationdate ,mdtacom.apple.quicktime.location.ISO6709 $mdtacom.apple.quicktime.software !mdtacom.apple.quicktime.model ilst   data DEApple 0  (data DE2011-05-11T17:54:04+0200 2  *data DE+52.4936+013.3897+040.247/   data DE4.3.1  data DEiPhone 4 + // http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt + + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom(substr($atom_data, 4), $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); + //$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); + break; + + case 'data': // metaDATA atom + // seems to be 2 bytes language code (ASCII), 2 bytes unknown (set to 0x10B5 in sample I have), remainder is useful data + $atom_structure['language'] = substr($atom_data, 4 + 0, 2); + $atom_structure['unknown'] = getid3_lib::BigEndian2Int(substr($atom_data, 4 + 2, 2)); + $atom_structure['data'] = substr($atom_data, 4 + 4); + break; + + default: + $info['warning'][] = 'Unknown QuickTime atom type: "'.getid3_lib::PrintHexBytes($atomname).'" at offset '.$baseoffset; + $atom_structure['data'] = $atom_data; + break; + } + array_pop($atomHierarchy); + return $atom_structure; + } + + function QuicktimeParseContainerAtom($atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) { +//echo 'QuicktimeParseContainerAtom('.substr($atom_data, 4, 4).') @ '.$baseoffset.'

'; + $atom_structure = false; + $subatomoffset = 0; + $subatomcounter = 0; + if ((strlen($atom_data) == 4) && (getid3_lib::BigEndian2Int($atom_data) == 0x00000000)) { + return false; + } + while ($subatomoffset < strlen($atom_data)) { + $subatomsize = getid3_lib::BigEndian2Int(substr($atom_data, $subatomoffset + 0, 4)); + $subatomname = substr($atom_data, $subatomoffset + 4, 4); + $subatomdata = substr($atom_data, $subatomoffset + 8, $subatomsize - 8); + if ($subatomsize == 0) { + // Furthermore, for historical reasons the list of atoms is optionally + // terminated by a 32-bit integer set to 0. If you are writing a program + // to read user data atoms, you should allow for the terminating 0. + return $atom_structure; + } + + $atom_structure[$subatomcounter] = $this->QuicktimeParseAtom($subatomname, $subatomsize, $subatomdata, $baseoffset + $subatomoffset, $atomHierarchy, $ParseAllPossibleAtoms); + + $subatomoffset += $subatomsize; + $subatomcounter++; + } + return $atom_structure; + } + + + function quicktime_read_mp4_descr_length($data, &$offset) { + // http://libquicktime.sourcearchive.com/documentation/2:1.0.2plus-pdebian-2build1/esds_8c-source.html + $num_bytes = 0; + $length = 0; + do { + $b = ord(substr($data, $offset++, 1)); + $length = ($length << 7) | ($b & 0x7F); + } while (($b & 0x80) && ($num_bytes++ < 4)); + return $length; + } + + + function QuicktimeLanguageLookup($languageid) { + static $QuicktimeLanguageLookup = array(); + if (empty($QuicktimeLanguageLookup)) { + $QuicktimeLanguageLookup[0] = 'English'; + $QuicktimeLanguageLookup[1] = 'French'; + $QuicktimeLanguageLookup[2] = 'German'; + $QuicktimeLanguageLookup[3] = 'Italian'; + $QuicktimeLanguageLookup[4] = 'Dutch'; + $QuicktimeLanguageLookup[5] = 'Swedish'; + $QuicktimeLanguageLookup[6] = 'Spanish'; + $QuicktimeLanguageLookup[7] = 'Danish'; + $QuicktimeLanguageLookup[8] = 'Portuguese'; + $QuicktimeLanguageLookup[9] = 'Norwegian'; + $QuicktimeLanguageLookup[10] = 'Hebrew'; + $QuicktimeLanguageLookup[11] = 'Japanese'; + $QuicktimeLanguageLookup[12] = 'Arabic'; + $QuicktimeLanguageLookup[13] = 'Finnish'; + $QuicktimeLanguageLookup[14] = 'Greek'; + $QuicktimeLanguageLookup[15] = 'Icelandic'; + $QuicktimeLanguageLookup[16] = 'Maltese'; + $QuicktimeLanguageLookup[17] = 'Turkish'; + $QuicktimeLanguageLookup[18] = 'Croatian'; + $QuicktimeLanguageLookup[19] = 'Chinese (Traditional)'; + $QuicktimeLanguageLookup[20] = 'Urdu'; + $QuicktimeLanguageLookup[21] = 'Hindi'; + $QuicktimeLanguageLookup[22] = 'Thai'; + $QuicktimeLanguageLookup[23] = 'Korean'; + $QuicktimeLanguageLookup[24] = 'Lithuanian'; + $QuicktimeLanguageLookup[25] = 'Polish'; + $QuicktimeLanguageLookup[26] = 'Hungarian'; + $QuicktimeLanguageLookup[27] = 'Estonian'; + $QuicktimeLanguageLookup[28] = 'Lettish'; + $QuicktimeLanguageLookup[28] = 'Latvian'; + $QuicktimeLanguageLookup[29] = 'Saamisk'; + $QuicktimeLanguageLookup[29] = 'Lappish'; + $QuicktimeLanguageLookup[30] = 'Faeroese'; + $QuicktimeLanguageLookup[31] = 'Farsi'; + $QuicktimeLanguageLookup[31] = 'Persian'; + $QuicktimeLanguageLookup[32] = 'Russian'; + $QuicktimeLanguageLookup[33] = 'Chinese (Simplified)'; + $QuicktimeLanguageLookup[34] = 'Flemish'; + $QuicktimeLanguageLookup[35] = 'Irish'; + $QuicktimeLanguageLookup[36] = 'Albanian'; + $QuicktimeLanguageLookup[37] = 'Romanian'; + $QuicktimeLanguageLookup[38] = 'Czech'; + $QuicktimeLanguageLookup[39] = 'Slovak'; + $QuicktimeLanguageLookup[40] = 'Slovenian'; + $QuicktimeLanguageLookup[41] = 'Yiddish'; + $QuicktimeLanguageLookup[42] = 'Serbian'; + $QuicktimeLanguageLookup[43] = 'Macedonian'; + $QuicktimeLanguageLookup[44] = 'Bulgarian'; + $QuicktimeLanguageLookup[45] = 'Ukrainian'; + $QuicktimeLanguageLookup[46] = 'Byelorussian'; + $QuicktimeLanguageLookup[47] = 'Uzbek'; + $QuicktimeLanguageLookup[48] = 'Kazakh'; + $QuicktimeLanguageLookup[49] = 'Azerbaijani'; + $QuicktimeLanguageLookup[50] = 'AzerbaijanAr'; + $QuicktimeLanguageLookup[51] = 'Armenian'; + $QuicktimeLanguageLookup[52] = 'Georgian'; + $QuicktimeLanguageLookup[53] = 'Moldavian'; + $QuicktimeLanguageLookup[54] = 'Kirghiz'; + $QuicktimeLanguageLookup[55] = 'Tajiki'; + $QuicktimeLanguageLookup[56] = 'Turkmen'; + $QuicktimeLanguageLookup[57] = 'Mongolian'; + $QuicktimeLanguageLookup[58] = 'MongolianCyr'; + $QuicktimeLanguageLookup[59] = 'Pashto'; + $QuicktimeLanguageLookup[60] = 'Kurdish'; + $QuicktimeLanguageLookup[61] = 'Kashmiri'; + $QuicktimeLanguageLookup[62] = 'Sindhi'; + $QuicktimeLanguageLookup[63] = 'Tibetan'; + $QuicktimeLanguageLookup[64] = 'Nepali'; + $QuicktimeLanguageLookup[65] = 'Sanskrit'; + $QuicktimeLanguageLookup[66] = 'Marathi'; + $QuicktimeLanguageLookup[67] = 'Bengali'; + $QuicktimeLanguageLookup[68] = 'Assamese'; + $QuicktimeLanguageLookup[69] = 'Gujarati'; + $QuicktimeLanguageLookup[70] = 'Punjabi'; + $QuicktimeLanguageLookup[71] = 'Oriya'; + $QuicktimeLanguageLookup[72] = 'Malayalam'; + $QuicktimeLanguageLookup[73] = 'Kannada'; + $QuicktimeLanguageLookup[74] = 'Tamil'; + $QuicktimeLanguageLookup[75] = 'Telugu'; + $QuicktimeLanguageLookup[76] = 'Sinhalese'; + $QuicktimeLanguageLookup[77] = 'Burmese'; + $QuicktimeLanguageLookup[78] = 'Khmer'; + $QuicktimeLanguageLookup[79] = 'Lao'; + $QuicktimeLanguageLookup[80] = 'Vietnamese'; + $QuicktimeLanguageLookup[81] = 'Indonesian'; + $QuicktimeLanguageLookup[82] = 'Tagalog'; + $QuicktimeLanguageLookup[83] = 'MalayRoman'; + $QuicktimeLanguageLookup[84] = 'MalayArabic'; + $QuicktimeLanguageLookup[85] = 'Amharic'; + $QuicktimeLanguageLookup[86] = 'Tigrinya'; + $QuicktimeLanguageLookup[87] = 'Galla'; + $QuicktimeLanguageLookup[87] = 'Oromo'; + $QuicktimeLanguageLookup[88] = 'Somali'; + $QuicktimeLanguageLookup[89] = 'Swahili'; + $QuicktimeLanguageLookup[90] = 'Ruanda'; + $QuicktimeLanguageLookup[91] = 'Rundi'; + $QuicktimeLanguageLookup[92] = 'Chewa'; + $QuicktimeLanguageLookup[93] = 'Malagasy'; + $QuicktimeLanguageLookup[94] = 'Esperanto'; + $QuicktimeLanguageLookup[128] = 'Welsh'; + $QuicktimeLanguageLookup[129] = 'Basque'; + $QuicktimeLanguageLookup[130] = 'Catalan'; + $QuicktimeLanguageLookup[131] = 'Latin'; + $QuicktimeLanguageLookup[132] = 'Quechua'; + $QuicktimeLanguageLookup[133] = 'Guarani'; + $QuicktimeLanguageLookup[134] = 'Aymara'; + $QuicktimeLanguageLookup[135] = 'Tatar'; + $QuicktimeLanguageLookup[136] = 'Uighur'; + $QuicktimeLanguageLookup[137] = 'Dzongkha'; + $QuicktimeLanguageLookup[138] = 'JavaneseRom'; + } + return (isset($QuicktimeLanguageLookup[$languageid]) ? $QuicktimeLanguageLookup[$languageid] : 'invalid'); + } + + function QuicktimeVideoCodecLookup($codecid) { + static $QuicktimeVideoCodecLookup = array(); + if (empty($QuicktimeVideoCodecLookup)) { + $QuicktimeVideoCodecLookup['.SGI'] = 'SGI'; + $QuicktimeVideoCodecLookup['3IV1'] = '3ivx MPEG-4 v1'; + $QuicktimeVideoCodecLookup['3IV2'] = '3ivx MPEG-4 v2'; + $QuicktimeVideoCodecLookup['3IVX'] = '3ivx MPEG-4'; + $QuicktimeVideoCodecLookup['8BPS'] = 'Planar RGB'; + $QuicktimeVideoCodecLookup['avc1'] = 'H.264/MPEG-4 AVC'; + $QuicktimeVideoCodecLookup['avr '] = 'AVR-JPEG'; + $QuicktimeVideoCodecLookup['b16g'] = '16Gray'; + $QuicktimeVideoCodecLookup['b32a'] = '32AlphaGray'; + $QuicktimeVideoCodecLookup['b48r'] = '48RGB'; + $QuicktimeVideoCodecLookup['b64a'] = '64ARGB'; + $QuicktimeVideoCodecLookup['base'] = 'Base'; + $QuicktimeVideoCodecLookup['clou'] = 'Cloud'; + $QuicktimeVideoCodecLookup['cmyk'] = 'CMYK'; + $QuicktimeVideoCodecLookup['cvid'] = 'Cinepak'; + $QuicktimeVideoCodecLookup['dmb1'] = 'OpenDML JPEG'; + $QuicktimeVideoCodecLookup['dvc '] = 'DVC-NTSC'; + $QuicktimeVideoCodecLookup['dvcp'] = 'DVC-PAL'; + $QuicktimeVideoCodecLookup['dvpn'] = 'DVCPro-NTSC'; + $QuicktimeVideoCodecLookup['dvpp'] = 'DVCPro-PAL'; + $QuicktimeVideoCodecLookup['fire'] = 'Fire'; + $QuicktimeVideoCodecLookup['flic'] = 'FLC'; + $QuicktimeVideoCodecLookup['gif '] = 'GIF'; + $QuicktimeVideoCodecLookup['h261'] = 'H261'; + $QuicktimeVideoCodecLookup['h263'] = 'H263'; + $QuicktimeVideoCodecLookup['IV41'] = 'Indeo4'; + $QuicktimeVideoCodecLookup['jpeg'] = 'JPEG'; + $QuicktimeVideoCodecLookup['kpcd'] = 'PhotoCD'; + $QuicktimeVideoCodecLookup['mjpa'] = 'Motion JPEG-A'; + $QuicktimeVideoCodecLookup['mjpb'] = 'Motion JPEG-B'; + $QuicktimeVideoCodecLookup['msvc'] = 'Microsoft Video1'; + $QuicktimeVideoCodecLookup['myuv'] = 'MPEG YUV420'; + $QuicktimeVideoCodecLookup['path'] = 'Vector'; + $QuicktimeVideoCodecLookup['png '] = 'PNG'; + $QuicktimeVideoCodecLookup['PNTG'] = 'MacPaint'; + $QuicktimeVideoCodecLookup['qdgx'] = 'QuickDrawGX'; + $QuicktimeVideoCodecLookup['qdrw'] = 'QuickDraw'; + $QuicktimeVideoCodecLookup['raw '] = 'RAW'; + $QuicktimeVideoCodecLookup['ripl'] = 'WaterRipple'; + $QuicktimeVideoCodecLookup['rpza'] = 'Video'; + $QuicktimeVideoCodecLookup['smc '] = 'Graphics'; + $QuicktimeVideoCodecLookup['SVQ1'] = 'Sorenson Video 1'; + $QuicktimeVideoCodecLookup['SVQ1'] = 'Sorenson Video 3'; + $QuicktimeVideoCodecLookup['syv9'] = 'Sorenson YUV9'; + $QuicktimeVideoCodecLookup['tga '] = 'Targa'; + $QuicktimeVideoCodecLookup['tiff'] = 'TIFF'; + $QuicktimeVideoCodecLookup['WRAW'] = 'Windows RAW'; + $QuicktimeVideoCodecLookup['WRLE'] = 'BMP'; + $QuicktimeVideoCodecLookup['y420'] = 'YUV420'; + $QuicktimeVideoCodecLookup['yuv2'] = 'ComponentVideo'; + $QuicktimeVideoCodecLookup['yuvs'] = 'ComponentVideoUnsigned'; + $QuicktimeVideoCodecLookup['yuvu'] = 'ComponentVideoSigned'; + } + return (isset($QuicktimeVideoCodecLookup[$codecid]) ? $QuicktimeVideoCodecLookup[$codecid] : ''); + } + + function QuicktimeAudioCodecLookup($codecid) { + static $QuicktimeAudioCodecLookup = array(); + if (empty($QuicktimeAudioCodecLookup)) { + $QuicktimeAudioCodecLookup['.mp3'] = 'Fraunhofer MPEG Layer-III alias'; + $QuicktimeAudioCodecLookup['aac '] = 'ISO/IEC 14496-3 AAC'; + $QuicktimeAudioCodecLookup['agsm'] = 'Apple GSM 10:1'; + $QuicktimeAudioCodecLookup['alac'] = 'Apple Lossless Audio Codec'; + $QuicktimeAudioCodecLookup['alaw'] = 'A-law 2:1'; + $QuicktimeAudioCodecLookup['conv'] = 'Sample Format'; + $QuicktimeAudioCodecLookup['dvca'] = 'DV'; + $QuicktimeAudioCodecLookup['dvi '] = 'DV 4:1'; + $QuicktimeAudioCodecLookup['eqal'] = 'Frequency Equalizer'; + $QuicktimeAudioCodecLookup['fl32'] = '32-bit Floating Point'; + $QuicktimeAudioCodecLookup['fl64'] = '64-bit Floating Point'; + $QuicktimeAudioCodecLookup['ima4'] = 'Interactive Multimedia Association 4:1'; + $QuicktimeAudioCodecLookup['in24'] = '24-bit Integer'; + $QuicktimeAudioCodecLookup['in32'] = '32-bit Integer'; + $QuicktimeAudioCodecLookup['lpc '] = 'LPC 23:1'; + $QuicktimeAudioCodecLookup['MAC3'] = 'Macintosh Audio Compression/Expansion (MACE) 3:1'; + $QuicktimeAudioCodecLookup['MAC6'] = 'Macintosh Audio Compression/Expansion (MACE) 6:1'; + $QuicktimeAudioCodecLookup['mixb'] = '8-bit Mixer'; + $QuicktimeAudioCodecLookup['mixw'] = '16-bit Mixer'; + $QuicktimeAudioCodecLookup['mp4a'] = 'ISO/IEC 14496-3 AAC'; + $QuicktimeAudioCodecLookup['MS'."\x00\x02"] = 'Microsoft ADPCM'; + $QuicktimeAudioCodecLookup['MS'."\x00\x11"] = 'DV IMA'; + $QuicktimeAudioCodecLookup['MS'."\x00\x55"] = 'Fraunhofer MPEG Layer III'; + $QuicktimeAudioCodecLookup['NONE'] = 'No Encoding'; + $QuicktimeAudioCodecLookup['Qclp'] = 'Qualcomm PureVoice'; + $QuicktimeAudioCodecLookup['QDM2'] = 'QDesign Music 2'; + $QuicktimeAudioCodecLookup['QDMC'] = 'QDesign Music 1'; + $QuicktimeAudioCodecLookup['ratb'] = '8-bit Rate'; + $QuicktimeAudioCodecLookup['ratw'] = '16-bit Rate'; + $QuicktimeAudioCodecLookup['raw '] = 'raw PCM'; + $QuicktimeAudioCodecLookup['sour'] = 'Sound Source'; + $QuicktimeAudioCodecLookup['sowt'] = 'signed/two\'s complement (Little Endian)'; + $QuicktimeAudioCodecLookup['str1'] = 'Iomega MPEG layer II'; + $QuicktimeAudioCodecLookup['str2'] = 'Iomega MPEG *layer II'; + $QuicktimeAudioCodecLookup['str3'] = 'Iomega MPEG **layer II'; + $QuicktimeAudioCodecLookup['str4'] = 'Iomega MPEG ***layer II'; + $QuicktimeAudioCodecLookup['twos'] = 'signed/two\'s complement (Big Endian)'; + $QuicktimeAudioCodecLookup['ulaw'] = 'mu-law 2:1'; + } + return (isset($QuicktimeAudioCodecLookup[$codecid]) ? $QuicktimeAudioCodecLookup[$codecid] : ''); + } + + function QuicktimeDCOMLookup($compressionid) { + static $QuicktimeDCOMLookup = array(); + if (empty($QuicktimeDCOMLookup)) { + $QuicktimeDCOMLookup['zlib'] = 'ZLib Deflate'; + $QuicktimeDCOMLookup['adec'] = 'Apple Compression'; + } + return (isset($QuicktimeDCOMLookup[$compressionid]) ? $QuicktimeDCOMLookup[$compressionid] : ''); + } + + function QuicktimeColorNameLookup($colordepthid) { + static $QuicktimeColorNameLookup = array(); + if (empty($QuicktimeColorNameLookup)) { + $QuicktimeColorNameLookup[1] = '2-color (monochrome)'; + $QuicktimeColorNameLookup[2] = '4-color'; + $QuicktimeColorNameLookup[4] = '16-color'; + $QuicktimeColorNameLookup[8] = '256-color'; + $QuicktimeColorNameLookup[16] = 'thousands (16-bit color)'; + $QuicktimeColorNameLookup[24] = 'millions (24-bit color)'; + $QuicktimeColorNameLookup[32] = 'millions+ (32-bit color)'; + $QuicktimeColorNameLookup[33] = 'black & white'; + $QuicktimeColorNameLookup[34] = '4-gray'; + $QuicktimeColorNameLookup[36] = '16-gray'; + $QuicktimeColorNameLookup[40] = '256-gray'; + } + return (isset($QuicktimeColorNameLookup[$colordepthid]) ? $QuicktimeColorNameLookup[$colordepthid] : 'invalid'); + } + + function QuicktimeSTIKLookup($stik) { + static $QuicktimeSTIKLookup = array(); + if (empty($QuicktimeSTIKLookup)) { + $QuicktimeSTIKLookup[0] = 'Movie'; + $QuicktimeSTIKLookup[1] = 'Normal'; + $QuicktimeSTIKLookup[2] = 'Audiobook'; + $QuicktimeSTIKLookup[5] = 'Whacked Bookmark'; + $QuicktimeSTIKLookup[6] = 'Music Video'; + $QuicktimeSTIKLookup[9] = 'Short Film'; + $QuicktimeSTIKLookup[10] = 'TV Show'; + $QuicktimeSTIKLookup[11] = 'Booklet'; + $QuicktimeSTIKLookup[14] = 'Ringtone'; + $QuicktimeSTIKLookup[21] = 'Podcast'; + } + return (isset($QuicktimeSTIKLookup[$stik]) ? $QuicktimeSTIKLookup[$stik] : 'invalid'); + } + + function QuicktimeIODSaudioProfileName($audio_profile_id) { + static $QuicktimeIODSaudioProfileNameLookup = array(); + if (empty($QuicktimeIODSaudioProfileNameLookup)) { + $QuicktimeIODSaudioProfileNameLookup = array( + 0x00 => 'ISO Reserved (0x00)', + 0x01 => 'Main Audio Profile @ Level 1', + 0x02 => 'Main Audio Profile @ Level 2', + 0x03 => 'Main Audio Profile @ Level 3', + 0x04 => 'Main Audio Profile @ Level 4', + 0x05 => 'Scalable Audio Profile @ Level 1', + 0x06 => 'Scalable Audio Profile @ Level 2', + 0x07 => 'Scalable Audio Profile @ Level 3', + 0x08 => 'Scalable Audio Profile @ Level 4', + 0x09 => 'Speech Audio Profile @ Level 1', + 0x0A => 'Speech Audio Profile @ Level 2', + 0x0B => 'Synthetic Audio Profile @ Level 1', + 0x0C => 'Synthetic Audio Profile @ Level 2', + 0x0D => 'Synthetic Audio Profile @ Level 3', + 0x0E => 'High Quality Audio Profile @ Level 1', + 0x0F => 'High Quality Audio Profile @ Level 2', + 0x10 => 'High Quality Audio Profile @ Level 3', + 0x11 => 'High Quality Audio Profile @ Level 4', + 0x12 => 'High Quality Audio Profile @ Level 5', + 0x13 => 'High Quality Audio Profile @ Level 6', + 0x14 => 'High Quality Audio Profile @ Level 7', + 0x15 => 'High Quality Audio Profile @ Level 8', + 0x16 => 'Low Delay Audio Profile @ Level 1', + 0x17 => 'Low Delay Audio Profile @ Level 2', + 0x18 => 'Low Delay Audio Profile @ Level 3', + 0x19 => 'Low Delay Audio Profile @ Level 4', + 0x1A => 'Low Delay Audio Profile @ Level 5', + 0x1B => 'Low Delay Audio Profile @ Level 6', + 0x1C => 'Low Delay Audio Profile @ Level 7', + 0x1D => 'Low Delay Audio Profile @ Level 8', + 0x1E => 'Natural Audio Profile @ Level 1', + 0x1F => 'Natural Audio Profile @ Level 2', + 0x20 => 'Natural Audio Profile @ Level 3', + 0x21 => 'Natural Audio Profile @ Level 4', + 0x22 => 'Mobile Audio Internetworking Profile @ Level 1', + 0x23 => 'Mobile Audio Internetworking Profile @ Level 2', + 0x24 => 'Mobile Audio Internetworking Profile @ Level 3', + 0x25 => 'Mobile Audio Internetworking Profile @ Level 4', + 0x26 => 'Mobile Audio Internetworking Profile @ Level 5', + 0x27 => 'Mobile Audio Internetworking Profile @ Level 6', + 0x28 => 'AAC Profile @ Level 1', + 0x29 => 'AAC Profile @ Level 2', + 0x2A => 'AAC Profile @ Level 4', + 0x2B => 'AAC Profile @ Level 5', + 0x2C => 'High Efficiency AAC Profile @ Level 2', + 0x2D => 'High Efficiency AAC Profile @ Level 3', + 0x2E => 'High Efficiency AAC Profile @ Level 4', + 0x2F => 'High Efficiency AAC Profile @ Level 5', + 0xFE => 'Not part of MPEG-4 audio profiles', + 0xFF => 'No audio capability required', + ); + } + return (isset($QuicktimeIODSaudioProfileNameLookup[$audio_profile_id]) ? $QuicktimeIODSaudioProfileNameLookup[$audio_profile_id] : 'ISO Reserved / User Private'); + } + + + function QuicktimeIODSvideoProfileName($video_profile_id) { + static $QuicktimeIODSvideoProfileNameLookup = array(); + if (empty($QuicktimeIODSvideoProfileNameLookup)) { + $QuicktimeIODSvideoProfileNameLookup = array( + 0x00 => 'Reserved (0x00) Profile', + 0x01 => 'Simple Profile @ Level 1', + 0x02 => 'Simple Profile @ Level 2', + 0x03 => 'Simple Profile @ Level 3', + 0x08 => 'Simple Profile @ Level 0', + 0x10 => 'Simple Scalable Profile @ Level 0', + 0x11 => 'Simple Scalable Profile @ Level 1', + 0x12 => 'Simple Scalable Profile @ Level 2', + 0x15 => 'AVC/H264 Profile', + 0x21 => 'Core Profile @ Level 1', + 0x22 => 'Core Profile @ Level 2', + 0x32 => 'Main Profile @ Level 2', + 0x33 => 'Main Profile @ Level 3', + 0x34 => 'Main Profile @ Level 4', + 0x42 => 'N-bit Profile @ Level 2', + 0x51 => 'Scalable Texture Profile @ Level 1', + 0x61 => 'Simple Face Animation Profile @ Level 1', + 0x62 => 'Simple Face Animation Profile @ Level 2', + 0x63 => 'Simple FBA Profile @ Level 1', + 0x64 => 'Simple FBA Profile @ Level 2', + 0x71 => 'Basic Animated Texture Profile @ Level 1', + 0x72 => 'Basic Animated Texture Profile @ Level 2', + 0x81 => 'Hybrid Profile @ Level 1', + 0x82 => 'Hybrid Profile @ Level 2', + 0x91 => 'Advanced Real Time Simple Profile @ Level 1', + 0x92 => 'Advanced Real Time Simple Profile @ Level 2', + 0x93 => 'Advanced Real Time Simple Profile @ Level 3', + 0x94 => 'Advanced Real Time Simple Profile @ Level 4', + 0xA1 => 'Core Scalable Profile @ Level1', + 0xA2 => 'Core Scalable Profile @ Level2', + 0xA3 => 'Core Scalable Profile @ Level3', + 0xB1 => 'Advanced Coding Efficiency Profile @ Level 1', + 0xB2 => 'Advanced Coding Efficiency Profile @ Level 2', + 0xB3 => 'Advanced Coding Efficiency Profile @ Level 3', + 0xB4 => 'Advanced Coding Efficiency Profile @ Level 4', + 0xC1 => 'Advanced Core Profile @ Level 1', + 0xC2 => 'Advanced Core Profile @ Level 2', + 0xD1 => 'Advanced Scalable Texture @ Level1', + 0xD2 => 'Advanced Scalable Texture @ Level2', + 0xE1 => 'Simple Studio Profile @ Level 1', + 0xE2 => 'Simple Studio Profile @ Level 2', + 0xE3 => 'Simple Studio Profile @ Level 3', + 0xE4 => 'Simple Studio Profile @ Level 4', + 0xE5 => 'Core Studio Profile @ Level 1', + 0xE6 => 'Core Studio Profile @ Level 2', + 0xE7 => 'Core Studio Profile @ Level 3', + 0xE8 => 'Core Studio Profile @ Level 4', + 0xF0 => 'Advanced Simple Profile @ Level 0', + 0xF1 => 'Advanced Simple Profile @ Level 1', + 0xF2 => 'Advanced Simple Profile @ Level 2', + 0xF3 => 'Advanced Simple Profile @ Level 3', + 0xF4 => 'Advanced Simple Profile @ Level 4', + 0xF5 => 'Advanced Simple Profile @ Level 5', + 0xF7 => 'Advanced Simple Profile @ Level 3b', + 0xF8 => 'Fine Granularity Scalable Profile @ Level 0', + 0xF9 => 'Fine Granularity Scalable Profile @ Level 1', + 0xFA => 'Fine Granularity Scalable Profile @ Level 2', + 0xFB => 'Fine Granularity Scalable Profile @ Level 3', + 0xFC => 'Fine Granularity Scalable Profile @ Level 4', + 0xFD => 'Fine Granularity Scalable Profile @ Level 5', + 0xFE => 'Not part of MPEG-4 Visual profiles', + 0xFF => 'No visual capability required', + ); + } + return (isset($QuicktimeIODSvideoProfileNameLookup[$video_profile_id]) ? $QuicktimeIODSvideoProfileNameLookup[$video_profile_id] : 'ISO Reserved Profile'); + } + + + function QuicktimeContentRatingLookup($rtng) { + static $QuicktimeContentRatingLookup = array(); + if (empty($QuicktimeContentRatingLookup)) { + $QuicktimeContentRatingLookup[0] = 'None'; + $QuicktimeContentRatingLookup[2] = 'Clean'; + $QuicktimeContentRatingLookup[4] = 'Explicit'; + } + return (isset($QuicktimeContentRatingLookup[$rtng]) ? $QuicktimeContentRatingLookup[$rtng] : 'invalid'); + } + + function QuicktimeStoreAccountTypeLookup($akid) { + static $QuicktimeStoreAccountTypeLookup = array(); + if (empty($QuicktimeStoreAccountTypeLookup)) { + $QuicktimeStoreAccountTypeLookup[0] = 'iTunes'; + $QuicktimeStoreAccountTypeLookup[1] = 'AOL'; + } + return (isset($QuicktimeStoreAccountTypeLookup[$akid]) ? $QuicktimeStoreAccountTypeLookup[$akid] : 'invalid'); + } + + function QuicktimeStoreFrontCodeLookup($sfid) { + static $QuicktimeStoreFrontCodeLookup = array(); + if (empty($QuicktimeStoreFrontCodeLookup)) { + $QuicktimeStoreFrontCodeLookup[143460] = 'Australia'; + $QuicktimeStoreFrontCodeLookup[143445] = 'Austria'; + $QuicktimeStoreFrontCodeLookup[143446] = 'Belgium'; + $QuicktimeStoreFrontCodeLookup[143455] = 'Canada'; + $QuicktimeStoreFrontCodeLookup[143458] = 'Denmark'; + $QuicktimeStoreFrontCodeLookup[143447] = 'Finland'; + $QuicktimeStoreFrontCodeLookup[143442] = 'France'; + $QuicktimeStoreFrontCodeLookup[143443] = 'Germany'; + $QuicktimeStoreFrontCodeLookup[143448] = 'Greece'; + $QuicktimeStoreFrontCodeLookup[143449] = 'Ireland'; + $QuicktimeStoreFrontCodeLookup[143450] = 'Italy'; + $QuicktimeStoreFrontCodeLookup[143462] = 'Japan'; + $QuicktimeStoreFrontCodeLookup[143451] = 'Luxembourg'; + $QuicktimeStoreFrontCodeLookup[143452] = 'Netherlands'; + $QuicktimeStoreFrontCodeLookup[143461] = 'New Zealand'; + $QuicktimeStoreFrontCodeLookup[143457] = 'Norway'; + $QuicktimeStoreFrontCodeLookup[143453] = 'Portugal'; + $QuicktimeStoreFrontCodeLookup[143454] = 'Spain'; + $QuicktimeStoreFrontCodeLookup[143456] = 'Sweden'; + $QuicktimeStoreFrontCodeLookup[143459] = 'Switzerland'; + $QuicktimeStoreFrontCodeLookup[143444] = 'United Kingdom'; + $QuicktimeStoreFrontCodeLookup[143441] = 'United States'; + } + return (isset($QuicktimeStoreFrontCodeLookup[$sfid]) ? $QuicktimeStoreFrontCodeLookup[$sfid] : 'invalid'); + } + + function QuicktimeParseNikonNCTG($atom_data) { + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG + // Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100 + // Data is stored as records of: + // * 4 bytes record type + // * 2 bytes size of data field type: + // 0x0001 = flag (size field *= 1-byte) + // 0x0002 = char (size field *= 1-byte) + // 0x0003 = DWORD+ (size field *= 2-byte), values are stored CDAB + // 0x0004 = QWORD+ (size field *= 4-byte), values are stored EFGHABCD + // 0x0005 = float (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together + // 0x0007 = bytes (size field *= 1-byte), values are stored as ?????? + // 0x0008 = ????? (size field *= 2-byte), values are stored as ?????? + // * 2 bytes data size field + // * ? bytes data (string data may be null-padded; datestamp fields are in the format "2011:05:25 20:24:15") + // all integers are stored BigEndian + + $NCTGtagName = array( + 0x00000001 => 'Make', + 0x00000002 => 'Model', + 0x00000003 => 'Software', + 0x00000011 => 'CreateDate', + 0x00000012 => 'DateTimeOriginal', + 0x00000013 => 'FrameCount', + 0x00000016 => 'FrameRate', + 0x00000022 => 'FrameWidth', + 0x00000023 => 'FrameHeight', + 0x00000032 => 'AudioChannels', + 0x00000033 => 'AudioBitsPerSample', + 0x00000034 => 'AudioSampleRate', + 0x02000001 => 'MakerNoteVersion', + 0x02000005 => 'WhiteBalance', + 0x0200000b => 'WhiteBalanceFineTune', + 0x0200001e => 'ColorSpace', + 0x02000023 => 'PictureControlData', + 0x02000024 => 'WorldTime', + 0x02000032 => 'UnknownInfo', + 0x02000083 => 'LensType', + 0x02000084 => 'Lens', + ); + + $offset = 0; + $datalength = strlen($atom_data); + $parsed = array(); + while ($offset < $datalength) { +//echo getid3_lib::PrintHexBytes(substr($atom_data, $offset, 4)).'
'; + $record_type = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 4)); $offset += 4; + $data_size_type = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); $offset += 2; + $data_size = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); $offset += 2; + switch ($data_size_type) { + case 0x0001: // 0x0001 = flag (size field *= 1-byte) + $data = getid3_lib::BigEndian2Int(substr($atom_data, $offset, $data_size * 1)); + $offset += ($data_size * 1); + break; + case 0x0002: // 0x0002 = char (size field *= 1-byte) + $data = substr($atom_data, $offset, $data_size * 1); + $offset += ($data_size * 1); + $data = rtrim($data, "\x00"); + break; + case 0x0003: // 0x0003 = DWORD+ (size field *= 2-byte), values are stored CDAB + $data = ''; + for ($i = $data_size - 1; $i >= 0; $i--) { + $data .= substr($atom_data, $offset + ($i * 2), 2); + } + $data = getid3_lib::BigEndian2Int($data); + $offset += ($data_size * 2); + break; + case 0x0004: // 0x0004 = QWORD+ (size field *= 4-byte), values are stored EFGHABCD + $data = ''; + for ($i = $data_size - 1; $i >= 0; $i--) { + $data .= substr($atom_data, $offset + ($i * 4), 4); + } + $data = getid3_lib::BigEndian2Int($data); + $offset += ($data_size * 4); + break; + case 0x0005: // 0x0005 = float (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together + $data = array(); + for ($i = 0; $i < $data_size; $i++) { + $numerator = getid3_lib::BigEndian2Int(substr($atom_data, $offset + ($i * 8) + 0, 4)); + $denomninator = getid3_lib::BigEndian2Int(substr($atom_data, $offset + ($i * 8) + 4, 4)); + if ($denomninator == 0) { + $data[$i] = false; + } else { + $data[$i] = (double) $numerator / $denomninator; + } + } + $offset += (8 * $data_size); + if (count($data) == 1) { + $data = $data[0]; + } + break; + case 0x0007: // 0x0007 = bytes (size field *= 1-byte), values are stored as ?????? + $data = substr($atom_data, $offset, $data_size * 1); + $offset += ($data_size * 1); + break; + case 0x0008: // 0x0008 = ????? (size field *= 2-byte), values are stored as ?????? + $data = substr($atom_data, $offset, $data_size * 2); + $offset += ($data_size * 2); + break; + default: +echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'
'; + break 2; + } + + switch ($record_type) { + case 0x00000011: // CreateDate + case 0x00000012: // DateTimeOriginal + $data = strtotime($data); + break; + case 0x0200001e: // ColorSpace + switch ($data) { + case 1: + $data = 'sRGB'; + break; + case 2: + $data = 'Adobe RGB'; + break; + } + break; + case 0x02000023: // PictureControlData + $PictureControlAdjust = array(0=>'default', 1=>'quick', 2=>'full'); + $FilterEffect = array(0x80=>'off', 0x81=>'yellow', 0x82=>'orange', 0x83=>'red', 0x84=>'green', 0xff=>'n/a'); + $ToningEffect = array(0x80=>'b&w', 0x81=>'sepia', 0x82=>'cyanotype', 0x83=>'red', 0x84=>'yellow', 0x85=>'green', 0x86=>'blue-green', 0x87=>'blue', 0x88=>'purple-blue', 0x89=>'red-purple', 0xff=>'n/a'); + $data = array( + 'PictureControlVersion' => substr($data, 0, 4), + 'PictureControlName' => rtrim(substr($data, 4, 20), "\x00"), + 'PictureControlBase' => rtrim(substr($data, 24, 20), "\x00"), + //'?' => substr($data, 44, 4), + 'PictureControlAdjust' => $PictureControlAdjust[ord(substr($data, 48, 1))], + 'PictureControlQuickAdjust' => ord(substr($data, 49, 1)), + 'Sharpness' => ord(substr($data, 50, 1)), + 'Contrast' => ord(substr($data, 51, 1)), + 'Brightness' => ord(substr($data, 52, 1)), + 'Saturation' => ord(substr($data, 53, 1)), + 'HueAdjustment' => ord(substr($data, 54, 1)), + 'FilterEffect' => $FilterEffect[ord(substr($data, 55, 1))], + 'ToningEffect' => $ToningEffect[ord(substr($data, 56, 1))], + 'ToningSaturation' => ord(substr($data, 57, 1)), + ); + break; + case 0x02000024: // WorldTime + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#WorldTime + // timezone is stored as offset from GMT in minutes + $timezone = getid3_lib::BigEndian2Int(substr($data, 0, 2)); + if ($timezone & 0x8000) { + $timezone = 0 - (0x10000 - $timezone); + } + $timezone /= 60; + + $dst = (bool) getid3_lib::BigEndian2Int(substr($data, 2, 1)); + switch (getid3_lib::BigEndian2Int(substr($data, 3, 1))) { + case 2: + $datedisplayformat = 'D/M/Y'; break; + case 1: + $datedisplayformat = 'M/D/Y'; break; + case 0: + default: + $datedisplayformat = 'Y/M/D'; break; + } + + $data = array('timezone'=>floatval($timezone), 'dst'=>$dst, 'display'=>$datedisplayformat); + break; + case 0x02000083: // LensType + $data = array( + //'_' => $data, + 'mf' => (bool) ($data & 0x01), + 'd' => (bool) ($data & 0x02), + 'g' => (bool) ($data & 0x04), + 'vr' => (bool) ($data & 0x08), + ); + break; + } + $tag_name = (isset($NCTGtagName[$record_type]) ? $NCTGtagName[$record_type] : '0x'.str_pad(dechex($record_type), 8, '0', STR_PAD_LEFT)); + $parsed[$tag_name] = $data; + } + return $parsed; + } + + + function CopyToAppropriateCommentsSection($keyname, $data, $boxname='') { + static $handyatomtranslatorarray = array(); + if (empty($handyatomtranslatorarray)) { + $handyatomtranslatorarray['©cpy'] = 'copyright'; + $handyatomtranslatorarray['©day'] = 'creation_date'; // iTunes 4.0 + $handyatomtranslatorarray['©dir'] = 'director'; + $handyatomtranslatorarray['©ed1'] = 'edit1'; + $handyatomtranslatorarray['©ed2'] = 'edit2'; + $handyatomtranslatorarray['©ed3'] = 'edit3'; + $handyatomtranslatorarray['©ed4'] = 'edit4'; + $handyatomtranslatorarray['©ed5'] = 'edit5'; + $handyatomtranslatorarray['©ed6'] = 'edit6'; + $handyatomtranslatorarray['©ed7'] = 'edit7'; + $handyatomtranslatorarray['©ed8'] = 'edit8'; + $handyatomtranslatorarray['©ed9'] = 'edit9'; + $handyatomtranslatorarray['©fmt'] = 'format'; + $handyatomtranslatorarray['©inf'] = 'information'; + $handyatomtranslatorarray['©prd'] = 'producer'; + $handyatomtranslatorarray['©prf'] = 'performers'; + $handyatomtranslatorarray['©req'] = 'system_requirements'; + $handyatomtranslatorarray['©src'] = 'source_credit'; + $handyatomtranslatorarray['©wrt'] = 'writer'; + + // http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt + $handyatomtranslatorarray['©nam'] = 'title'; // iTunes 4.0 + $handyatomtranslatorarray['©cmt'] = 'comment'; // iTunes 4.0 + $handyatomtranslatorarray['©wrn'] = 'warning'; + $handyatomtranslatorarray['©hst'] = 'host_computer'; + $handyatomtranslatorarray['©mak'] = 'make'; + $handyatomtranslatorarray['©mod'] = 'model'; + $handyatomtranslatorarray['©PRD'] = 'product'; + $handyatomtranslatorarray['©swr'] = 'software'; + $handyatomtranslatorarray['©aut'] = 'author'; + $handyatomtranslatorarray['©ART'] = 'artist'; + $handyatomtranslatorarray['©trk'] = 'track'; + $handyatomtranslatorarray['©alb'] = 'album'; // iTunes 4.0 + $handyatomtranslatorarray['©com'] = 'comment'; + $handyatomtranslatorarray['©gen'] = 'genre'; // iTunes 4.0 + $handyatomtranslatorarray['©ope'] = 'composer'; + $handyatomtranslatorarray['©url'] = 'url'; + $handyatomtranslatorarray['©enc'] = 'encoder'; + + // http://atomicparsley.sourceforge.net/mpeg-4files.html + $handyatomtranslatorarray['©art'] = 'artist'; // iTunes 4.0 + $handyatomtranslatorarray['aART'] = 'album_artist'; + $handyatomtranslatorarray['trkn'] = 'track_number'; // iTunes 4.0 + $handyatomtranslatorarray['disk'] = 'disc_number'; // iTunes 4.0 + $handyatomtranslatorarray['gnre'] = 'genre'; // iTunes 4.0 + $handyatomtranslatorarray['©too'] = 'encoder'; // iTunes 4.0 + $handyatomtranslatorarray['tmpo'] = 'bpm'; // iTunes 4.0 + $handyatomtranslatorarray['cprt'] = 'copyright'; // iTunes 4.0? + $handyatomtranslatorarray['cpil'] = 'compilation'; // iTunes 4.0 + $handyatomtranslatorarray['covr'] = 'picture'; // iTunes 4.0 + $handyatomtranslatorarray['rtng'] = 'rating'; // iTunes 4.0 + $handyatomtranslatorarray['©grp'] = 'grouping'; // iTunes 4.2 + $handyatomtranslatorarray['stik'] = 'stik'; // iTunes 4.9 + $handyatomtranslatorarray['pcst'] = 'podcast'; // iTunes 4.9 + $handyatomtranslatorarray['catg'] = 'category'; // iTunes 4.9 + $handyatomtranslatorarray['keyw'] = 'keyword'; // iTunes 4.9 + $handyatomtranslatorarray['purl'] = 'podcast_url'; // iTunes 4.9 + $handyatomtranslatorarray['egid'] = 'episode_guid'; // iTunes 4.9 + $handyatomtranslatorarray['desc'] = 'description'; // iTunes 5.0 + $handyatomtranslatorarray['©lyr'] = 'lyrics'; // iTunes 5.0 + $handyatomtranslatorarray['tvnn'] = 'tv_network_name'; // iTunes 6.0 + $handyatomtranslatorarray['tvsh'] = 'tv_show_name'; // iTunes 6.0 + $handyatomtranslatorarray['tvsn'] = 'tv_season'; // iTunes 6.0 + $handyatomtranslatorarray['tves'] = 'tv_episode'; // iTunes 6.0 + $handyatomtranslatorarray['purd'] = 'purchase_date'; // iTunes 6.0.2 + $handyatomtranslatorarray['pgap'] = 'gapless_playback'; // iTunes 7.0 + + // http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt + + + + // boxnames: + $handyatomtranslatorarray['iTunSMPB'] = 'iTunSMPB'; + $handyatomtranslatorarray['iTunNORM'] = 'iTunNORM'; + $handyatomtranslatorarray['Encoding Params'] = 'Encoding Params'; + $handyatomtranslatorarray['replaygain_track_gain'] = 'replaygain_track_gain'; + $handyatomtranslatorarray['replaygain_track_peak'] = 'replaygain_track_peak'; + $handyatomtranslatorarray['replaygain_track_minmax'] = 'replaygain_track_minmax'; + $handyatomtranslatorarray['MusicIP PUID'] = 'MusicIP PUID'; + $handyatomtranslatorarray['MusicBrainz Artist Id'] = 'MusicBrainz Artist Id'; + $handyatomtranslatorarray['MusicBrainz Album Id'] = 'MusicBrainz Album Id'; + $handyatomtranslatorarray['MusicBrainz Album Artist Id'] = 'MusicBrainz Album Artist Id'; + $handyatomtranslatorarray['MusicBrainz Track Id'] = 'MusicBrainz Track Id'; + $handyatomtranslatorarray['MusicBrainz Disc Id'] = 'MusicBrainz Disc Id'; + } + $info = &$this->getid3->info; + $comment_key = ''; + if ($boxname && ($boxname != $keyname) && isset($handyatomtranslatorarray[$boxname])) { + $comment_key = $handyatomtranslatorarray[$boxname]; + } elseif (isset($handyatomtranslatorarray[$keyname])) { + $comment_key = $handyatomtranslatorarray[$keyname]; + } + if ($comment_key) { + if ($comment_key == 'picture') { + if (!is_array($data)) { + $image_mime = ''; + if (preg_match('#^\x89\x50\x4E\x47\x0D\x0A\x1A\x0A#', $data)) { + $image_mime = 'image/png'; + } elseif (preg_match('#^\xFF\xD8\xFF#', $data)) { + $image_mime = 'image/jpeg'; + } elseif (preg_match('#^GIF#', $data)) { + $image_mime = 'image/gif'; + } elseif (preg_match('#^BM#', $data)) { + $image_mime = 'image/bmp'; + } + $data = array('data'=>$data, 'image_mime'=>$image_mime); + } + } + $info['quicktime']['comments'][$comment_key][] = $data; + } + return true; + } + + function NoNullString($nullterminatedstring) { + // remove the single null terminator on null terminated strings + if (substr($nullterminatedstring, strlen($nullterminatedstring) - 1, 1) === "\x00") { + return substr($nullterminatedstring, 0, strlen($nullterminatedstring) - 1); + } + return $nullterminatedstring; + } + + function Pascal2String($pascalstring) { + // Pascal strings have 1 unsigned byte at the beginning saying how many chars (1-255) are in the string + return substr($pascalstring, 1); + } + +} + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/module.audio-video.real.php b/3rdparty/getid3/module.audio-video.real.php similarity index 80% rename from apps/media/getID3/getid3/module.audio-video.real.php rename to 3rdparty/getid3/module.audio-video.real.php index 013f4784bc..d716e7da3c 100644 --- a/apps/media/getID3/getid3/module.audio-video.real.php +++ b/3rdparty/getid3/module.audio-video.real.php @@ -15,67 +15,69 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); -class getid3_real +class getid3_real extends getid3_handler { - function getid3_real(&$fd, &$ThisFileInfo) { - $ThisFileInfo['fileformat'] = 'real'; - $ThisFileInfo['bitrate'] = 0; - $ThisFileInfo['playtime_seconds'] = 0; + function Analyze() { + $info = &$this->getid3->info; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $info['fileformat'] = 'real'; + $info['bitrate'] = 0; + $info['playtime_seconds'] = 0; + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $ChunkCounter = 0; - while (ftell($fd) < $ThisFileInfo['avdataend']) { - $ChunkData = fread($fd, 8); + 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($fd, $ChunkSize - 8); - if ($this->ParseOldRAheader(substr($ChunkData, 0, 128), $ThisFileInfo['real']['old_ra_header'])) { - $ThisFileInfo['audio']['dataformat'] = 'real'; - $ThisFileInfo['audio']['lossless'] = false; - $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['real']['old_ra_header']['sample_rate']; - $ThisFileInfo['audio']['bits_per_sample'] = $ThisFileInfo['real']['old_ra_header']['bits_per_sample']; - $ThisFileInfo['audio']['channels'] = $ThisFileInfo['real']['old_ra_header']['channels']; + $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']; - $ThisFileInfo['playtime_seconds'] = 60 * ($ThisFileInfo['real']['old_ra_header']['audio_bytes'] / $ThisFileInfo['real']['old_ra_header']['bytes_per_minute']); - $ThisFileInfo['audio']['bitrate'] = 8 * ($ThisFileInfo['real']['old_ra_header']['audio_bytes'] / $ThisFileInfo['playtime_seconds']); - $ThisFileInfo['audio']['codec'] = $this->RealAudioCodecFourCClookup($ThisFileInfo['real']['old_ra_header']['fourcc'], $ThisFileInfo['audio']['bitrate']); + $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 ($ThisFileInfo['real']['old_ra_header']['comments'] as $key => $valuearray) { + foreach ($info['real']['old_ra_header']['comments'] as $key => $valuearray) { if (strlen(trim($valuearray[0])) > 0) { - $ThisFileInfo['real']['comments'][$key][] = trim($valuearray[0]); + $info['real']['comments'][$key][] = trim($valuearray[0]); } } return true; } - $ThisFileInfo['error'][] = 'There was a problem parsing this RealAudio file. Please submit it for analysis to http://www.getid3.org/upload/ or info@getid3.org'; - unset($ThisFileInfo['bitrate']); - unset($ThisFileInfo['playtime_seconds']); + $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 - $ThisFileInfo['real']['chunks'][$ChunkCounter] = array(); - $thisfile_real_chunks_currentchunk = &$ThisFileInfo['real']['chunks'][$ChunkCounter]; + $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($fd) - 8; + $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']) > $ThisFileInfo['avdataend']) { - $ThisFileInfo['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'; + 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 > (GETID3_FREAD_BUFFER_SIZE + 8)) { + if ($ChunkSize > ($this->getid3->fread_buffer_size() + 8)) { - $ChunkData .= fread($fd, GETID3_FREAD_BUFFER_SIZE - 8); - fseek($fd, $thisfile_real_chunks_currentchunk['offset'] + $ChunkSize, SEEK_SET); + $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($fd, $ChunkSize - 8); + + $ChunkData .= fread($this->getid3->fp, $ChunkSize - 8); } $offset = 8; @@ -95,7 +97,7 @@ class getid3_real break; default: - //$ThisFileInfo['warning'][] = 'Expected .RMF-object_version to be "0", actual value is "'.$thisfile_real_chunks_currentchunk['object_version'].'" (should not be a problem)'; + //$info['warning'][] = 'Expected .RMF-object_version to be "0", actual value is "'.$thisfile_real_chunks_currentchunk['object_version'].'" (should not be a problem)'; break; } @@ -128,9 +130,9 @@ class getid3_real $offset += 2; $thisfile_real_chunks_currentchunk['flags_raw'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2)); $offset += 2; - $ThisFileInfo['playtime_seconds'] = $thisfile_real_chunks_currentchunk['duration'] / 1000; + $info['playtime_seconds'] = $thisfile_real_chunks_currentchunk['duration'] / 1000; if ($thisfile_real_chunks_currentchunk['duration'] > 0) { - $ThisFileInfo['bitrate'] += $thisfile_real_chunks_currentchunk['avg_bit_rate']; + $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); @@ -202,24 +204,24 @@ class getid3_real $thisfile_real_chunks_currentchunk_videoinfo['codec'] = getid3_riff::RIFFfourccLookup($thisfile_real_chunks_currentchunk_videoinfo['fourcc2']); - $ThisFileInfo['video']['resolution_x'] = $thisfile_real_chunks_currentchunk_videoinfo['width']; - $ThisFileInfo['video']['resolution_y'] = $thisfile_real_chunks_currentchunk_videoinfo['height']; - $ThisFileInfo['video']['frame_rate'] = (float) $thisfile_real_chunks_currentchunk_videoinfo['frames_per_second']; - $ThisFileInfo['video']['codec'] = $thisfile_real_chunks_currentchunk_videoinfo['codec']; - $ThisFileInfo['video']['bits_per_sample'] = $thisfile_real_chunks_currentchunk_videoinfo['bits_per_sample']; + $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']); - $ThisFileInfo['audio']['sample_rate'] = $thisfile_real_chunks_currentchunk['parsed_audio_data']['sample_rate']; - $ThisFileInfo['audio']['bits_per_sample'] = $thisfile_real_chunks_currentchunk['parsed_audio_data']['bits_per_sample']; - $ThisFileInfo['audio']['channels'] = $thisfile_real_chunks_currentchunk['parsed_audio_data']['channels']; - if (!empty($ThisFileInfo['audio']['dataformat'])) { - foreach ($ThisFileInfo['audio'] as $key => $value) { + $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') { - $ThisFileInfo['audio']['streams'][$thisfile_real_chunks_currentchunk['stream_number']][$key] = $value; + $info['audio']['streams'][$thisfile_real_chunks_currentchunk['stream_number']][$key] = $value; } } } @@ -255,36 +257,36 @@ class getid3_real } - if (empty($ThisFileInfo['playtime_seconds'])) { - $ThisFileInfo['playtime_seconds'] = max($ThisFileInfo['playtime_seconds'], ($thisfile_real_chunks_currentchunk['duration'] + $thisfile_real_chunks_currentchunk['start_time']) / 1000); + 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': - $ThisFileInfo['audio']['bitrate'] = (isset($ThisFileInfo['audio']['bitrate']) ? $ThisFileInfo['audio']['bitrate'] : 0) + $thisfile_real_chunks_currentchunk['avg_bit_rate']; - $ThisFileInfo['audio']['codec'] = $this->RealAudioCodecFourCClookup($thisfile_real_chunks_currentchunk['parsed_audio_data']['fourcc'], $ThisFileInfo['audio']['bitrate']); - $ThisFileInfo['audio']['dataformat'] = 'real'; - $ThisFileInfo['audio']['lossless'] = false; + $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': - $ThisFileInfo['video']['bitrate'] = (isset($ThisFileInfo['video']['bitrate']) ? $ThisFileInfo['video']['bitrate'] : 0) + $thisfile_real_chunks_currentchunk['avg_bit_rate']; - $ThisFileInfo['video']['bitrate_mode'] = 'cbr'; - $ThisFileInfo['video']['dataformat'] = 'real'; - $ThisFileInfo['video']['lossless'] = false; - $ThisFileInfo['video']['pixel_aspect_ratio'] = (float) 1; + $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': - $ThisFileInfo['audio']['bitrate'] = (isset($ThisFileInfo['audio']['bitrate']) ? $ThisFileInfo['audio']['bitrate'] : 0) + $thisfile_real_chunks_currentchunk['avg_bit_rate']; - $ThisFileInfo['audio']['codec'] = 'RealAudio Lossless'; - $ThisFileInfo['audio']['dataformat'] = 'real'; - $ThisFileInfo['audio']['lossless'] = true; + $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; } - $ThisFileInfo['bitrate'] = (isset($ThisFileInfo['video']['bitrate']) ? $ThisFileInfo['video']['bitrate'] : 0) + (isset($ThisFileInfo['audio']['bitrate']) ? $ThisFileInfo['audio']['bitrate'] : 0); + $info['bitrate'] = (isset($info['video']['bitrate']) ? $info['video']['bitrate'] : 0) + (isset($info['audio']['bitrate']) ? $info['audio']['bitrate'] : 0); } } break; @@ -317,7 +319,7 @@ class getid3_real $commentkeystocopy = array('title'=>'title', 'artist'=>'artist', 'copyright'=>'copyright', 'comment'=>'comment'); foreach ($commentkeystocopy as $key => $val) { if ($thisfile_real_chunks_currentchunk[$key]) { - $ThisFileInfo['real']['comments'][$val][] = trim($thisfile_real_chunks_currentchunk[$key]); + $info['real']['comments'][$val][] = trim($thisfile_real_chunks_currentchunk[$key]); } } @@ -345,22 +347,22 @@ class getid3_real break 2; } else { // non-last index chunk, seek to next index chunk (skipping actual index data) - fseek($fd, $thisfile_real_chunks_currentchunk['next_index_header'], SEEK_SET); + fseek($this->getid3->fp, $thisfile_real_chunks_currentchunk['next_index_header'], SEEK_SET); } } break; default: - $ThisFileInfo['warning'][] = 'Unhandled RealMedia chunk "'.$ChunkName.'" at offset '.$thisfile_real_chunks_currentchunk['offset']; + $info['warning'][] = 'Unhandled RealMedia chunk "'.$ChunkName.'" at offset '.$thisfile_real_chunks_currentchunk['offset']; break; } $ChunkCounter++; } - if (!empty($ThisFileInfo['audio']['streams'])) { - $ThisFileInfo['audio']['bitrate'] = 0; - foreach ($ThisFileInfo['audio']['streams'] as $key => $valuearray) { - $ThisFileInfo['audio']['bitrate'] += $valuearray['bitrate']; + if (!empty($info['audio']['streams'])) { + $info['audio']['bitrate'] = 0; + foreach ($info['audio']['streams'] as $key => $valuearray) { + $info['audio']['bitrate'] += $valuearray['bitrate']; } } diff --git a/apps/media/getID3/getid3/module.audio-video.riff.php b/3rdparty/getid3/module.audio-video.riff.php similarity index 61% rename from apps/media/getID3/getid3/module.audio-video.riff.php rename to 3rdparty/getid3/module.audio-video.riff.php index 74ea33966c..8e8f53a403 100644 --- a/apps/media/getID3/getid3/module.audio-video.riff.php +++ b/3rdparty/getid3/module.audio-video.riff.php @@ -19,65 +19,115 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true); -class getid3_riff +class getid3_riff extends getid3_handler { - function getid3_riff(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; // initialize these values to an empty array, otherwise they default to NULL // and you can't append array values to a NULL value - $ThisFileInfo['riff'] = array('raw'=>array()); + $info['riff'] = array('raw'=>array()); // Shortcuts - $thisfile_riff = &$ThisFileInfo['riff']; + $thisfile_riff = &$info['riff']; $thisfile_riff_raw = &$thisfile_riff['raw']; - $thisfile_audio = &$ThisFileInfo['audio']; - $thisfile_video = &$ThisFileInfo['video']; - $thisfile_avdataoffset = &$ThisFileInfo['avdataoffset']; - $thisfile_avdataend = &$ThisFileInfo['avdataend']; + $thisfile_audio = &$info['audio']; + $thisfile_video = &$info['video']; $thisfile_audio_dataformat = &$thisfile_audio['dataformat']; $thisfile_riff_audio = &$thisfile_riff['audio']; $thisfile_riff_video = &$thisfile_riff['video']; - $Original['avdataoffset'] = $thisfile_avdataoffset; - $Original['avdataend'] = $thisfile_avdataend; + $Original['avdataoffset'] = $info['avdataoffset']; + $Original['avdataend'] = $info['avdataend']; - fseek($fd, $thisfile_avdataoffset, SEEK_SET); - $RIFFheader = fread($fd, 12); + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $RIFFheader = fread($this->getid3->fp, 12); $RIFFsubtype = substr($RIFFheader, 8, 4); switch (substr($RIFFheader, 0, 4)) { case 'FORM': - $ThisFileInfo['fileformat'] = 'aiff'; - $RIFFheaderSize = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($RIFFheader, 4, 4)); - $thisfile_riff[$RIFFsubtype] = getid3_riff::ParseRIFF($fd, $thisfile_avdataoffset + 12, $thisfile_avdataoffset + $RIFFheaderSize, $ThisFileInfo); - $thisfile_riff['header_size'] = $RIFFheaderSize; + $info['fileformat'] = 'aiff'; + $thisfile_riff['header_size'] = $this->EitherEndian2Int(substr($RIFFheader, 4, 4)); + $thisfile_riff[$RIFFsubtype] = $this->ParseRIFF($info['avdataoffset'] + 12, $info['avdataoffset'] + $thisfile_riff['header_size']); break; - case 'RIFF': + case 'RIFF': // AVI, WAV, etc case 'SDSS': // SDSS is identical to RIFF, just renamed. Used by SmartSound QuickTracks (www.smartsound.com) case 'RMP3': // RMP3 is identical to RIFF, just renamed. Used by [unknown program] when creating RIFF-MP3s + $info['fileformat'] = 'riff'; + $thisfile_riff['header_size'] = $this->EitherEndian2Int(substr($RIFFheader, 4, 4)); if ($RIFFsubtype == 'RMP3') { // RMP3 is identical to WAVE, just renamed. Used by [unknown program] when creating RIFF-MP3s $RIFFsubtype = 'WAVE'; } + $thisfile_riff[$RIFFsubtype] = $this->ParseRIFF($info['avdataoffset'] + 12, $info['avdataoffset'] + $thisfile_riff['header_size']); + if (($info['avdataend'] - $info['filesize']) == 1) { + // LiteWave appears to incorrectly *not* pad actual output file + // to nearest WORD boundary so may appear to be short by one + // byte, in which case - skip warning + $info['avdataend'] = $info['filesize']; + } - $ThisFileInfo['fileformat'] = 'riff'; - $RIFFheaderSize = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($RIFFheader, 4, 4)); - $thisfile_riff['header_size'] = $RIFFheaderSize; - $thisfile_riff[$RIFFsubtype] = getid3_riff::ParseRIFF($fd, $thisfile_avdataoffset + 12, $thisfile_avdataoffset + $RIFFheaderSize, $ThisFileInfo); + $nextRIFFoffset = $Original['avdataoffset'] + 8 + $thisfile_riff['header_size']; // 8 = "RIFF" + 32-bit offset + while ($nextRIFFoffset < min($info['filesize'], $info['avdataend'])) { + if (!getid3_lib::intValueSupported($nextRIFFoffset + 1024)) { + $info['error'][] = 'AVI extends beyond '.round(PHP_INT_MAX / 1073741824).'GB and PHP filesystem functions cannot read that far, playtime is probably wrong'; + $info['warning'][] = '[avdataend] value may be incorrect, multiple AVIX chunks may be present'; + break; + } else { + fseek($this->getid3->fp, $nextRIFFoffset, SEEK_SET); + $nextRIFFheader = fread($this->getid3->fp, 12); + if ($nextRIFFoffset == ($info['avdataend'] - 1)) { + if (substr($nextRIFFheader, 0, 1) == "\x00") { + // RIFF padded to WORD boundary, we're actually already at the end + break; + } + } + $nextRIFFheaderID = substr($nextRIFFheader, 0, 4); + $nextRIFFsize = $this->EitherEndian2Int(substr($nextRIFFheader, 4, 4)); + $nextRIFFtype = substr($nextRIFFheader, 8, 4); + $chunkdata = array(); + $chunkdata['offset'] = $nextRIFFoffset + 8; + $chunkdata['size'] = $nextRIFFsize; + $nextRIFFoffset = $chunkdata['offset'] + $chunkdata['size']; + switch ($nextRIFFheaderID) { + case 'RIFF': + $info['avdataend'] = $nextRIFFoffset; + if (!getid3_lib::intValueSupported($info['avdataend'])) { + $info['error'][] = 'AVI extends beyond '.round(PHP_INT_MAX / 1073741824).'GB and PHP filesystem functions cannot read that far, playtime is probably wrong'; + $info['warning'][] = '[avdataend] value may be incorrect, multiple AVIX chunks may be present'; + } + $chunkdata['chunks'] = $this->ParseRIFF($chunkdata['offset'] + 4, $chunkdata['offset'] + $chunkdata['size']); - fseek($fd, $thisfile_avdataoffset + $RIFFheaderSize); - $nextRIFFheader = fread($fd, 20); - if (substr($nextRIFFheader, 8, 4) == 'RIFF') { - $nextRIFFsize = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($nextRIFFheader, 12, 4)); - $nextRIFFtype = substr($nextRIFFheader, 16, 4).'
'; - $thisfile_riff[$nextRIFFtype]['offset'] = ftell($fd) - 4; - $thisfile_riff[$nextRIFFtype]['size'] = $nextRIFFsize; - $ThisFileInfo['avdataend'] = $thisfile_riff[$nextRIFFtype]['offset'] + $thisfile_riff[$nextRIFFtype]['size']; - $ThisFileInfo['error'][] = 'AVI extends beyond 2GB and PHP filesystem functions cannot read that far, playtime is probably wrong'; - $ThisFileInfo['warning'][] = '[avdataend] value may be incorrect, multiple AVIX chunks may be present'; - $thisfile_riff[$nextRIFFtype] = getid3_riff::ParseRIFF($fd, $thisfile_riff[$nextRIFFtype]['offset'] + 4, $thisfile_riff[$nextRIFFtype]['offset'] + $thisfile_riff[$nextRIFFtype]['size'], $ThisFileInfo); + if (!isset($thisfile_riff[$nextRIFFtype])) { + $thisfile_riff[$nextRIFFtype] = array(); + } + $thisfile_riff[$nextRIFFtype][] = $chunkdata; + break; + case 'JUNK': + // ignore + $thisfile_riff[$nextRIFFheaderID][] = $chunkdata; + break; + default: + if ($info['filesize'] == ($chunkdata['offset'] - 8 + 128)) { + $DIVXTAG = $nextRIFFheader.fread($this->getid3->fp, 128 - 12); + if (substr($DIVXTAG, -7) == 'DIVXTAG') { + // DIVXTAG is supposed to be inside an IDVX chunk in a LIST chunk, but some bad encoders just slap it on the end of a file + $info['warning'][] = 'Found wrongly-structured DIVXTAG at offset '.(ftell($this->getid3->fp) - 128 + 12).', parsing anyway'; + $thisfile_riff['DIVXTAG'] = $this->ParseDIVXTAG($DIVXTAG); + foreach ($thisfile_riff['DIVXTAG'] as $key => $value) { + if ($value && !preg_match('#_id$#', $key)) { + $thisfile_riff['comments'][$key][] = $value; + } + } + break 2; + } + } + $info['warning'][] = 'expecting "RIFF" or "JUNK" at '.$nextRIFFoffset.', found '.getid3_lib::PrintHexBytes(substr($nextRIFFheader, 0, 4)).' - skipping rest of file'; + break 2; + } + } } if ($RIFFsubtype == 'WAVE') { $thisfile_riff_WAVE = &$thisfile_riff['WAVE']; @@ -85,8 +135,8 @@ class getid3_riff break; default: - $ThisFileInfo['error'][] = 'Cannot parse RIFF (this is maybe not a RIFF / WAV / AVI file?) - expecting "FORM|RIFF|SDSS|RMP3" found "'.$RIFFsubtype.'" instead'; - unset($ThisFileInfo['fileformat']); + $info['error'][] = 'Cannot parse RIFF (this is maybe not a RIFF / WAV / AVI file?) - expecting "FORM|RIFF|SDSS|RMP3" found "'.$RIFFsubtype.'" instead'; + unset($info['fileformat']); return false; break; } @@ -102,15 +152,15 @@ class getid3_riff } if (isset($thisfile_riff_WAVE['data'][0]['offset'])) { - $thisfile_avdataoffset = $thisfile_riff_WAVE['data'][0]['offset'] + 8; - $thisfile_avdataend = $thisfile_avdataoffset + $thisfile_riff_WAVE['data'][0]['size']; + $info['avdataoffset'] = $thisfile_riff_WAVE['data'][0]['offset'] + 8; + $info['avdataend'] = $info['avdataoffset'] + $thisfile_riff_WAVE['data'][0]['size']; } if (isset($thisfile_riff_WAVE['fmt '][0]['data'])) { $thisfile_riff_audio[$streamindex] = getid3_riff::RIFFparseWAVEFORMATex($thisfile_riff_WAVE['fmt '][0]['data']); $thisfile_audio['wformattag'] = $thisfile_riff_audio[$streamindex]['raw']['wFormatTag']; - if (@$thisfile_riff_audio[$streamindex]['bitrate'] == 0) { - $ThisFileInfo['error'][] = 'Corrupt RIFF file: bitrate_audio == zero'; + if (!isset($thisfile_riff_audio[$streamindex]['bitrate']) || ($thisfile_riff_audio[$streamindex]['bitrate'] == 0)) { + $info['error'][] = 'Corrupt RIFF file: bitrate_audio == zero'; return false; } $thisfile_riff_raw['fmt '] = $thisfile_riff_audio[$streamindex]['raw']; @@ -119,11 +169,11 @@ class getid3_riff $thisfile_audio = getid3_lib::array_merge_noclobber($thisfile_audio, $thisfile_riff_audio[$streamindex]); if (substr($thisfile_audio['codec'], 0, strlen('unknown: 0x')) == 'unknown: 0x') { - $ThisFileInfo['warning'][] = 'Audio codec = '.$thisfile_audio['codec']; + $info['warning'][] = 'Audio codec = '.$thisfile_audio['codec']; } $thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate']; - $ThisFileInfo['playtime_seconds'] = (float) ((($thisfile_avdataend - $thisfile_avdataoffset) * 8) / $thisfile_audio['bitrate']); + $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $thisfile_audio['bitrate']); $thisfile_audio['lossless'] = false; if (isset($thisfile_riff_WAVE['data'][0]['offset']) && isset($thisfile_riff_raw['fmt ']['wFormatTag'])) { @@ -158,9 +208,9 @@ class getid3_riff $thisfile_riff_raw_rgad_track = &$thisfile_riff_raw_rgad['track']; $thisfile_riff_raw_rgad_album = &$thisfile_riff_raw_rgad['album']; - $thisfile_riff_raw_rgad['fPeakAmplitude'] = getid3_lib::LittleEndian2Float(substr($rgadData, 0, 4)); - $thisfile_riff_raw_rgad['nRadioRgAdjust'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($rgadData, 4, 2)); - $thisfile_riff_raw_rgad['nAudiophileRgAdjust'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($rgadData, 6, 2)); + $thisfile_riff_raw_rgad['fPeakAmplitude'] = getid3_lib::LittleEndian2Float(substr($rgadData, 0, 4)); + $thisfile_riff_raw_rgad['nRadioRgAdjust'] = $this->EitherEndian2Int(substr($rgadData, 4, 2)); + $thisfile_riff_raw_rgad['nAudiophileRgAdjust'] = $this->EitherEndian2Int(substr($rgadData, 6, 2)); $nRadioRgAdjustBitstring = str_pad(getid3_lib::Dec2Bin($thisfile_riff_raw_rgad['nRadioRgAdjust']), 16, '0', STR_PAD_LEFT); $nAudiophileRgAdjustBitstring = str_pad(getid3_lib::Dec2Bin($thisfile_riff_raw_rgad['nAudiophileRgAdjust']), 16, '0', STR_PAD_LEFT); @@ -187,14 +237,14 @@ class getid3_riff } if (isset($thisfile_riff_WAVE['fact'][0]['data'])) { - $thisfile_riff_raw['fact']['NumberOfSamples'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_WAVE['fact'][0]['data'], 0, 4)); + $thisfile_riff_raw['fact']['NumberOfSamples'] = $this->EitherEndian2Int(substr($thisfile_riff_WAVE['fact'][0]['data'], 0, 4)); // This should be a good way of calculating exact playtime, // but some sample files have had incorrect number of samples, // so cannot use this method // if (!empty($thisfile_riff_raw['fmt ']['nSamplesPerSec'])) { - // $ThisFileInfo['playtime_seconds'] = (float) $thisfile_riff_raw['fact']['NumberOfSamples'] / $thisfile_riff_raw['fmt ']['nSamplesPerSec']; + // $info['playtime_seconds'] = (float) $thisfile_riff_raw['fact']['NumberOfSamples'] / $thisfile_riff_raw['fmt ']['nSamplesPerSec']; // } } if (!empty($thisfile_riff_raw['fmt ']['nAvgBytesPerSec'])) { @@ -212,17 +262,19 @@ class getid3_riff $thisfile_riff_WAVE_bext_0['origin_time'] = substr($thisfile_riff_WAVE_bext_0['data'], 330, 8); $thisfile_riff_WAVE_bext_0['time_reference'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_bext_0['data'], 338, 8)); $thisfile_riff_WAVE_bext_0['bwf_version'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_bext_0['data'], 346, 1)); - $thisfile_riff_WAVE_bext_0['reserved'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_bext_0['data'], 347, 254)); + $thisfile_riff_WAVE_bext_0['reserved'] = substr($thisfile_riff_WAVE_bext_0['data'], 347, 254); $thisfile_riff_WAVE_bext_0['coding_history'] = explode("\r\n", trim(substr($thisfile_riff_WAVE_bext_0['data'], 601))); - - $thisfile_riff_WAVE_bext_0['origin_date_unix'] = gmmktime( - substr($thisfile_riff_WAVE_bext_0['origin_time'], 0, 2), - substr($thisfile_riff_WAVE_bext_0['origin_time'], 3, 2), - substr($thisfile_riff_WAVE_bext_0['origin_time'], 6, 2), - substr($thisfile_riff_WAVE_bext_0['origin_date'], 5, 2), - substr($thisfile_riff_WAVE_bext_0['origin_date'], 8, 2), - substr($thisfile_riff_WAVE_bext_0['origin_date'], 0, 4)); - + if (preg_match('#^([0-9]{4}).([0-9]{2}).([0-9]{2})$#', $thisfile_riff_WAVE_bext_0['origin_date'], $matches_bext_date)) { + if (preg_match('#^([0-9]{2}).([0-9]{2}).([0-9]{2})$#', $thisfile_riff_WAVE_bext_0['origin_time'], $matches_bext_time)) { + list($dummy, $bext_timestamp['year'], $bext_timestamp['month'], $bext_timestamp['day']) = $matches_bext_date; + list($dummy, $bext_timestamp['hour'], $bext_timestamp['minute'], $bext_timestamp['second']) = $matches_bext_time; + $thisfile_riff_WAVE_bext_0['origin_date_unix'] = gmmktime($bext_timestamp['hour'], $bext_timestamp['minute'], $bext_timestamp['second'], $bext_timestamp['month'], $bext_timestamp['day'], $bext_timestamp['year']); + } else { + $info['warning'][] = 'RIFF.WAVE.BEXT.origin_time is invalid'; + } + } else { + $info['warning'][] = 'RIFF.WAVE.BEXT.origin_date is invalid'; + } $thisfile_riff['comments']['author'][] = $thisfile_riff_WAVE_bext_0['author']; $thisfile_riff['comments']['title'][] = $thisfile_riff_WAVE_bext_0['title']; } @@ -278,40 +330,132 @@ class getid3_riff $thisfile_riff['comments']['title'][] = $thisfile_riff_WAVE_cart_0['title']; } - if (!isset($thisfile_audio['bitrate']) && isset($thisfile_riff_audio[$streamindex]['bitrate'])) { - $thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate']; - $ThisFileInfo['playtime_seconds'] = (float) ((($thisfile_avdataend - $thisfile_avdataoffset) * 8) / $thisfile_audio['bitrate']); + if (isset($thisfile_riff_WAVE['SNDM'][0]['data'])) { + // SoundMiner metadata + + // shortcuts + $thisfile_riff_WAVE_SNDM_0 = &$thisfile_riff_WAVE['SNDM'][0]; + $thisfile_riff_WAVE_SNDM_0_data = &$thisfile_riff_WAVE_SNDM_0['data']; + $SNDM_startoffset = 0; + $SNDM_endoffset = $thisfile_riff_WAVE_SNDM_0['size']; + + while ($SNDM_startoffset < $SNDM_endoffset) { + $SNDM_thisTagOffset = 0; + $SNDM_thisTagSize = getid3_lib::BigEndian2Int(substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 4)); + $SNDM_thisTagOffset += 4; + $SNDM_thisTagKey = substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 4); + $SNDM_thisTagOffset += 4; + $SNDM_thisTagDataSize = getid3_lib::BigEndian2Int(substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 2)); + $SNDM_thisTagOffset += 2; + $SNDM_thisTagDataFlags = getid3_lib::BigEndian2Int(substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 2)); + $SNDM_thisTagOffset += 2; + $SNDM_thisTagDataText = substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, $SNDM_thisTagDataSize); + $SNDM_thisTagOffset += $SNDM_thisTagDataSize; + + if ($SNDM_thisTagSize != (4 + 4 + 2 + 2 + $SNDM_thisTagDataSize)) { + $info['warning'][] = 'RIFF.WAVE.SNDM.data contains tag not expected length (expected: '.$SNDM_thisTagSize.', found: '.(4 + 4 + 2 + 2 + $SNDM_thisTagDataSize).') at offset '.$SNDM_startoffset.' (file offset '.($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset).')'; + break; + } elseif ($SNDM_thisTagSize <= 0) { + $info['warning'][] = 'RIFF.WAVE.SNDM.data contains zero-size tag at offset '.$SNDM_startoffset.' (file offset '.($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset).')'; + break; + } + $SNDM_startoffset += $SNDM_thisTagSize; + + $thisfile_riff_WAVE_SNDM_0['parsed_raw'][$SNDM_thisTagKey] = $SNDM_thisTagDataText; + if ($parsedkey = $this->RIFFwaveSNDMtagLookup($SNDM_thisTagKey)) { + $thisfile_riff_WAVE_SNDM_0['parsed'][$parsedkey] = $SNDM_thisTagDataText; + } else { + $info['warning'][] = 'RIFF.WAVE.SNDM contains unknown tag "'.$SNDM_thisTagKey.'" at offset '.$SNDM_startoffset.' (file offset '.($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset).')'; + } + } + + $tagmapping = array( + 'tracktitle'=>'title', + 'category' =>'genre', + 'cdtitle' =>'album', + 'tracktitle'=>'title', + ); + foreach ($tagmapping as $fromkey => $tokey) { + if (isset($thisfile_riff_WAVE_SNDM_0['parsed'][$fromkey])) { + $thisfile_riff['comments'][$tokey][] = $thisfile_riff_WAVE_SNDM_0['parsed'][$fromkey]; + } + } } - if (!empty($ThisFileInfo['wavpack'])) { + if (isset($thisfile_riff_WAVE['iXML'][0]['data'])) { + // requires functions simplexml_load_string and get_object_vars + if ($parsedXML = getid3_lib::XML2array($thisfile_riff_WAVE['iXML'][0]['data'])) { + $thisfile_riff_WAVE['iXML'][0]['parsed'] = $parsedXML; + if (isset($parsedXML['SPEED']['MASTER_SPEED'])) { + @list($numerator, $denominator) = explode('/', $parsedXML['SPEED']['MASTER_SPEED']); + $thisfile_riff_WAVE['iXML'][0]['master_speed'] = $numerator / ($denominator ? $denominator : 1000); + } + if (isset($parsedXML['SPEED']['TIMECODE_RATE'])) { + @list($numerator, $denominator) = explode('/', $parsedXML['SPEED']['TIMECODE_RATE']); + $thisfile_riff_WAVE['iXML'][0]['timecode_rate'] = $numerator / ($denominator ? $denominator : 1000); + } + if (isset($parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_LO']) && !empty($parsedXML['SPEED']['TIMESTAMP_SAMPLE_RATE']) && !empty($thisfile_riff_WAVE['iXML'][0]['timecode_rate'])) { + $samples_since_midnight = floatval(ltrim($parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_HI'].$parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_LO'], '0')); + $thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] = $samples_since_midnight / $parsedXML['SPEED']['TIMESTAMP_SAMPLE_RATE']; + $h = floor( $thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] / 3600); + $m = floor(($thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] - ($h * 3600)) / 60); + $s = floor( $thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] - ($h * 3600) - ($m * 60)); + $f = ($thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] - ($h * 3600) - ($m * 60) - $s) * $thisfile_riff_WAVE['iXML'][0]['timecode_rate']; + $thisfile_riff_WAVE['iXML'][0]['timecode_string'] = sprintf('%02d:%02d:%02d:%05.2f', $h, $m, $s, $f); + $thisfile_riff_WAVE['iXML'][0]['timecode_string_round'] = sprintf('%02d:%02d:%02d:%02d', $h, $m, $s, round($f)); + } + unset($parsedXML); + } + } + + + + if (!isset($thisfile_audio['bitrate']) && isset($thisfile_riff_audio[$streamindex]['bitrate'])) { + $thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate']; + $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $thisfile_audio['bitrate']); + } + + if (!empty($info['wavpack'])) { $thisfile_audio_dataformat = 'wavpack'; $thisfile_audio['bitrate_mode'] = 'vbr'; - $thisfile_audio['encoder'] = 'WavPack v'.$ThisFileInfo['wavpack']['version']; + $thisfile_audio['encoder'] = 'WavPack v'.$info['wavpack']['version']; // Reset to the way it was - RIFF parsing will have messed this up - $thisfile_avdataend = $Original['avdataend']; - $thisfile_audio['bitrate'] = (($thisfile_avdataend - $thisfile_avdataoffset) * 8) / $ThisFileInfo['playtime_seconds']; + $info['avdataend'] = $Original['avdataend']; + $thisfile_audio['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; - fseek($fd, $thisfile_avdataoffset - 44, SEEK_SET); - $RIFFdata = fread($fd, 44); + fseek($this->getid3->fp, $info['avdataoffset'] - 44, SEEK_SET); + $RIFFdata = fread($this->getid3->fp, 44); $OrignalRIFFheaderSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 4, 4)) + 8; $OrignalRIFFdataSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 40, 4)) + 44; if ($OrignalRIFFheaderSize > $OrignalRIFFdataSize) { - $thisfile_avdataend -= ($OrignalRIFFheaderSize - $OrignalRIFFdataSize); - fseek($fd, $thisfile_avdataend, SEEK_SET); - $RIFFdata .= fread($fd, $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_riff::ParseRIFFdata($RIFFdata, $ThisFileInfo); + $getid3_riff = new getid3_riff($this->getid3); + $getid3_riff->ParseRIFFdata($RIFFdata); + unset($getid3_riff); } if (isset($thisfile_riff_raw['fmt ']['wFormatTag'])) { switch ($thisfile_riff_raw['fmt ']['wFormatTag']) { + case 0x0001: // PCM + if (!empty($info['ac3'])) { + // Dolby Digital WAV files masquerade as PCM-WAV, but they're not + $thisfile_audio['wformattag'] = 0x2000; + $thisfile_audio['codec'] = $this->RIFFwFormatTagLookup($thisfile_audio['wformattag']); + $thisfile_audio['lossless'] = false; + $thisfile_audio['bitrate'] = $info['ac3']['bitrate']; + $thisfile_audio['sample_rate'] = $info['ac3']['sample_rate']; + } + break; case 0x08AE: // ClearJump LiteWave $thisfile_audio['bitrate_mode'] = 'vbr'; $thisfile_audio_dataformat = 'litewave'; @@ -363,8 +507,8 @@ class getid3_riff break; } } - if ($thisfile_avdataend > $ThisFileInfo['filesize']) { - switch (@$thisfile_audio_dataformat) { + if ($info['avdataend'] > $info['filesize']) { + switch (!empty($thisfile_audio_dataformat) ? $thisfile_audio_dataformat : '') { case 'wavpack': // WavPack case 'lpac': // LPAC case 'ofr': // OptimFROG @@ -373,41 +517,41 @@ class getid3_riff break; case 'litewave': - if (($thisfile_avdataend - $ThisFileInfo['filesize']) == 1) { + if (($info['avdataend'] - $info['filesize']) == 1) { // LiteWave appears to incorrectly *not* pad actual output file // to nearest WORD boundary so may appear to be short by one // byte, in which case - skip warning } else { // Short by more than one byte, throw warning - $ThisFileInfo['warning'][] = 'Probably truncated file - expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($ThisFileInfo['filesize'] - $thisfile_avdataoffset).' (short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($ThisFileInfo['filesize'] - $thisfile_avdataoffset)).' bytes)'; - $thisfile_avdataend = $ThisFileInfo['filesize']; + $info['warning'][] = 'Probably truncated file - expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' (short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])).' bytes)'; + $info['avdataend'] = $info['filesize']; } break; default: - if ((($thisfile_avdataend - $ThisFileInfo['filesize']) == 1) && (($thisfile_riff[$RIFFsubtype]['data'][0]['size'] % 2) == 0) && ((($ThisFileInfo['filesize'] - $thisfile_avdataoffset) % 2) == 1)) { + if ((($info['avdataend'] - $info['filesize']) == 1) && (($thisfile_riff[$RIFFsubtype]['data'][0]['size'] % 2) == 0) && ((($info['filesize'] - $info['avdataoffset']) % 2) == 1)) { // output file appears to be incorrectly *not* padded to nearest WORD boundary // Output less severe warning - $ThisFileInfo['warning'][] = 'File should probably be padded to nearest WORD boundary, but it is not (expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($ThisFileInfo['filesize'] - $thisfile_avdataoffset).' therefore short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($ThisFileInfo['filesize'] - $thisfile_avdataoffset)).' bytes)'; - $thisfile_avdataend = $ThisFileInfo['filesize']; - break; + $info['warning'][] = 'File should probably be padded to nearest WORD boundary, but it is not (expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' therefore short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])).' bytes)'; + $info['avdataend'] = $info['filesize']; + } else { + // Short by more than one byte, throw warning + $info['warning'][] = 'Probably truncated file - expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' (short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])).' bytes)'; + $info['avdataend'] = $info['filesize']; } - // Short by more than one byte, throw warning - $ThisFileInfo['warning'][] = 'Probably truncated file - expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($ThisFileInfo['filesize'] - $thisfile_avdataoffset).' (short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($ThisFileInfo['filesize'] - $thisfile_avdataoffset)).' bytes)'; - $thisfile_avdataend = $ThisFileInfo['filesize']; break; } } - if (!empty($ThisFileInfo['mpeg']['audio']['LAME']['audio_bytes'])) { - if ((($thisfile_avdataend - $thisfile_avdataoffset) - $ThisFileInfo['mpeg']['audio']['LAME']['audio_bytes']) == 1) { - $thisfile_avdataend--; - $ThisFileInfo['warning'][] = 'Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored'; + if (!empty($info['mpeg']['audio']['LAME']['audio_bytes'])) { + if ((($info['avdataend'] - $info['avdataoffset']) - $info['mpeg']['audio']['LAME']['audio_bytes']) == 1) { + $info['avdataend']--; + $info['warning'][] = 'Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored'; } } - if (@$thisfile_audio_dataformat == 'ac3') { + if (isset($thisfile_audio_dataformat) && ($thisfile_audio_dataformat == 'ac3')) { unset($thisfile_audio['bits_per_sample']); - if (!empty($ThisFileInfo['ac3']['bitrate']) && ($ThisFileInfo['ac3']['bitrate'] != $thisfile_audio['bitrate'])) { - $thisfile_audio['bitrate'] = $ThisFileInfo['ac3']['bitrate']; + if (!empty($info['ac3']['bitrate']) && ($info['ac3']['bitrate'] != $thisfile_audio['bitrate'])) { + $thisfile_audio['bitrate'] = $info['ac3']['bitrate']; } } break; @@ -415,14 +559,18 @@ class getid3_riff case 'AVI ': $thisfile_video['bitrate_mode'] = 'vbr'; // maybe not, but probably $thisfile_video['dataformat'] = 'avi'; - $ThisFileInfo['mime_type'] = 'video/avi'; + $info['mime_type'] = 'video/avi'; if (isset($thisfile_riff[$RIFFsubtype]['movi']['offset'])) { - $thisfile_avdataoffset = $thisfile_riff[$RIFFsubtype]['movi']['offset'] + 8; - $thisfile_avdataend = $thisfile_avdataoffset + $thisfile_riff[$RIFFsubtype]['movi']['size']; - if ($thisfile_avdataend > $ThisFileInfo['filesize']) { - $ThisFileInfo['warning'][] = 'Probably truncated file - expecting '.$thisfile_riff[$RIFFsubtype]['movi']['size'].' bytes of data, only found '.($ThisFileInfo['filesize'] - $thisfile_avdataoffset).' (short by '.($thisfile_riff[$RIFFsubtype]['movi']['size'] - ($ThisFileInfo['filesize'] - $thisfile_avdataoffset)).' bytes)'; - $thisfile_avdataend = $ThisFileInfo['filesize']; + $info['avdataoffset'] = $thisfile_riff[$RIFFsubtype]['movi']['offset'] + 8; + if (isset($thisfile_riff['AVIX'])) { + $info['avdataend'] = $thisfile_riff['AVIX'][(count($thisfile_riff['AVIX']) - 1)]['chunks']['movi']['offset'] + $thisfile_riff['AVIX'][(count($thisfile_riff['AVIX']) - 1)]['chunks']['movi']['size']; + } else { + $info['avdataend'] = $thisfile_riff['AVI ']['movi']['offset'] + $thisfile_riff['AVI ']['movi']['size']; + } + if ($info['avdataend'] > $info['filesize']) { + $info['warning'][] = 'Probably truncated file - expecting '.($info['avdataend'] - $info['avdataoffset']).' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' (short by '.($info['avdataend'] - $info['filesize']).' bytes)'; + $info['avdataend'] = $info['filesize']; } } @@ -440,15 +588,15 @@ class getid3_riff foreach ($thisfile_riff['AVI ']['hdrl']['strl']['indx'] as $streamnumber => $steamdataarray) { $thisfile_riff_avi_hdrl_strl_indx_stream_data = &$thisfile_riff['AVI ']['hdrl']['strl']['indx'][$streamnumber]['data']; - $thisfile_riff_raw['indx'][$streamnumber]['wLongsPerEntry'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 0, 2)); - $thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 2, 1)); - $thisfile_riff_raw['indx'][$streamnumber]['bIndexType'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 3, 1)); - $thisfile_riff_raw['indx'][$streamnumber]['nEntriesInUse'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 4, 4)); - $thisfile_riff_raw['indx'][$streamnumber]['dwChunkId'] = substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 8, 4); - $thisfile_riff_raw['indx'][$streamnumber]['dwReserved'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 12, 4)); + $thisfile_riff_raw['indx'][$streamnumber]['wLongsPerEntry'] = $this->EitherEndian2Int(substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 0, 2)); + $thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType'] = $this->EitherEndian2Int(substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 2, 1)); + $thisfile_riff_raw['indx'][$streamnumber]['bIndexType'] = $this->EitherEndian2Int(substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 3, 1)); + $thisfile_riff_raw['indx'][$streamnumber]['nEntriesInUse'] = $this->EitherEndian2Int(substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 4, 4)); + $thisfile_riff_raw['indx'][$streamnumber]['dwChunkId'] = substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 8, 4); + $thisfile_riff_raw['indx'][$streamnumber]['dwReserved'] = $this->EitherEndian2Int(substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 12, 4)); - //$thisfile_riff_raw['indx'][$streamnumber]['bIndexType_name'] = @$bIndexType[$thisfile_riff_raw['indx'][$streamnumber]['bIndexType']]; - //$thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType_name'] = @$bIndexSubtype[$thisfile_riff_raw['indx'][$streamnumber]['bIndexType']][$thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType']]; + //$thisfile_riff_raw['indx'][$streamnumber]['bIndexType_name'] = $bIndexType[$thisfile_riff_raw['indx'][$streamnumber]['bIndexType']]; + //$thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType_name'] = $bIndexSubtype[$thisfile_riff_raw['indx'][$streamnumber]['bIndexType']][$thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType']]; unset($thisfile_riff_avi_hdrl_strl_indx_stream_data); } @@ -460,24 +608,24 @@ class getid3_riff $thisfile_riff_raw['avih'] = array(); $thisfile_riff_raw_avih = &$thisfile_riff_raw['avih']; - $thisfile_riff_raw_avih['dwMicroSecPerFrame'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 0, 4)); // frame display rate (or 0L) + $thisfile_riff_raw_avih['dwMicroSecPerFrame'] = $this->EitherEndian2Int(substr($avihData, 0, 4)); // frame display rate (or 0L) if ($thisfile_riff_raw_avih['dwMicroSecPerFrame'] == 0) { - $ThisFileInfo['error'][] = 'Corrupt RIFF file: avih.dwMicroSecPerFrame == zero'; + $info['error'][] = 'Corrupt RIFF file: avih.dwMicroSecPerFrame == zero'; return false; } - $thisfile_riff_raw_avih['dwMaxBytesPerSec'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 4, 4)); // max. transfer rate - $thisfile_riff_raw_avih['dwPaddingGranularity'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 8, 4)); // pad to multiples of this size; normally 2K. - $thisfile_riff_raw_avih['dwFlags'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 12, 4)); // the ever-present flags - $thisfile_riff_raw_avih['dwTotalFrames'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 16, 4)); // # frames in file - $thisfile_riff_raw_avih['dwInitialFrames'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 20, 4)); - $thisfile_riff_raw_avih['dwStreams'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 24, 4)); - $thisfile_riff_raw_avih['dwSuggestedBufferSize'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 28, 4)); - $thisfile_riff_raw_avih['dwWidth'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 32, 4)); - $thisfile_riff_raw_avih['dwHeight'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 36, 4)); - $thisfile_riff_raw_avih['dwScale'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 40, 4)); - $thisfile_riff_raw_avih['dwRate'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 44, 4)); - $thisfile_riff_raw_avih['dwStart'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 48, 4)); - $thisfile_riff_raw_avih['dwLength'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 52, 4)); + $thisfile_riff_raw_avih['dwMaxBytesPerSec'] = $this->EitherEndian2Int(substr($avihData, 4, 4)); // max. transfer rate + $thisfile_riff_raw_avih['dwPaddingGranularity'] = $this->EitherEndian2Int(substr($avihData, 8, 4)); // pad to multiples of this size; normally 2K. + $thisfile_riff_raw_avih['dwFlags'] = $this->EitherEndian2Int(substr($avihData, 12, 4)); // the ever-present flags + $thisfile_riff_raw_avih['dwTotalFrames'] = $this->EitherEndian2Int(substr($avihData, 16, 4)); // # frames in file + $thisfile_riff_raw_avih['dwInitialFrames'] = $this->EitherEndian2Int(substr($avihData, 20, 4)); + $thisfile_riff_raw_avih['dwStreams'] = $this->EitherEndian2Int(substr($avihData, 24, 4)); + $thisfile_riff_raw_avih['dwSuggestedBufferSize'] = $this->EitherEndian2Int(substr($avihData, 28, 4)); + $thisfile_riff_raw_avih['dwWidth'] = $this->EitherEndian2Int(substr($avihData, 32, 4)); + $thisfile_riff_raw_avih['dwHeight'] = $this->EitherEndian2Int(substr($avihData, 36, 4)); + $thisfile_riff_raw_avih['dwScale'] = $this->EitherEndian2Int(substr($avihData, 40, 4)); + $thisfile_riff_raw_avih['dwRate'] = $this->EitherEndian2Int(substr($avihData, 44, 4)); + $thisfile_riff_raw_avih['dwStart'] = $this->EitherEndian2Int(substr($avihData, 48, 4)); + $thisfile_riff_raw_avih['dwLength'] = $this->EitherEndian2Int(substr($avihData, 52, 4)); $thisfile_riff_raw_avih['flags']['hasindex'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x00000010); $thisfile_riff_raw_avih['flags']['mustuseindex'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x00000020); @@ -595,20 +743,20 @@ class getid3_riff $thisfile_riff_raw['strh'][$i] = array(); $thisfile_riff_raw_strh_current = &$thisfile_riff_raw['strh'][$i]; - $thisfile_riff_raw_strh_current['fccType'] = substr($strhData, 0, 4); // same as $strhfccType; - $thisfile_riff_raw_strh_current['fccHandler'] = substr($strhData, 4, 4); - $thisfile_riff_raw_strh_current['dwFlags'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 8, 4)); // Contains AVITF_* flags - $thisfile_riff_raw_strh_current['wPriority'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 12, 2)); - $thisfile_riff_raw_strh_current['wLanguage'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 14, 2)); - $thisfile_riff_raw_strh_current['dwInitialFrames'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 16, 4)); - $thisfile_riff_raw_strh_current['dwScale'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 20, 4)); - $thisfile_riff_raw_strh_current['dwRate'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 24, 4)); - $thisfile_riff_raw_strh_current['dwStart'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 28, 4)); - $thisfile_riff_raw_strh_current['dwLength'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 32, 4)); - $thisfile_riff_raw_strh_current['dwSuggestedBufferSize'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 36, 4)); - $thisfile_riff_raw_strh_current['dwQuality'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 40, 4)); - $thisfile_riff_raw_strh_current['dwSampleSize'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 44, 4)); - $thisfile_riff_raw_strh_current['rcFrame'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 48, 4)); + $thisfile_riff_raw_strh_current['fccType'] = substr($strhData, 0, 4); // same as $strhfccType; + $thisfile_riff_raw_strh_current['fccHandler'] = substr($strhData, 4, 4); + $thisfile_riff_raw_strh_current['dwFlags'] = $this->EitherEndian2Int(substr($strhData, 8, 4)); // Contains AVITF_* flags + $thisfile_riff_raw_strh_current['wPriority'] = $this->EitherEndian2Int(substr($strhData, 12, 2)); + $thisfile_riff_raw_strh_current['wLanguage'] = $this->EitherEndian2Int(substr($strhData, 14, 2)); + $thisfile_riff_raw_strh_current['dwInitialFrames'] = $this->EitherEndian2Int(substr($strhData, 16, 4)); + $thisfile_riff_raw_strh_current['dwScale'] = $this->EitherEndian2Int(substr($strhData, 20, 4)); + $thisfile_riff_raw_strh_current['dwRate'] = $this->EitherEndian2Int(substr($strhData, 24, 4)); + $thisfile_riff_raw_strh_current['dwStart'] = $this->EitherEndian2Int(substr($strhData, 28, 4)); + $thisfile_riff_raw_strh_current['dwLength'] = $this->EitherEndian2Int(substr($strhData, 32, 4)); + $thisfile_riff_raw_strh_current['dwSuggestedBufferSize'] = $this->EitherEndian2Int(substr($strhData, 36, 4)); + $thisfile_riff_raw_strh_current['dwQuality'] = $this->EitherEndian2Int(substr($strhData, 40, 4)); + $thisfile_riff_raw_strh_current['dwSampleSize'] = $this->EitherEndian2Int(substr($strhData, 44, 4)); + $thisfile_riff_raw_strh_current['rcFrame'] = $this->EitherEndian2Int(substr($strhData, 48, 4)); $thisfile_riff_video_current['codec'] = getid3_riff::RIFFfourccLookup($thisfile_riff_raw_strh_current['fccHandler']); $thisfile_video['fourcc'] = $thisfile_riff_raw_strh_current['fccHandler']; @@ -632,19 +780,8 @@ class getid3_riff switch ($strhfccType) { case 'vids': - $thisfile_riff_raw_strf_strhfccType_streamindex = getid3_riff::ParseBITMAPINFOHEADER(substr($strfData, 0, 40), ($ThisFileInfo['fileformat'] == 'riff')); - //$thisfile_riff_raw_strf_strhfccType_streamindex['biSize'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 0, 4)); // number of bytes required by the BITMAPINFOHEADER structure - //$thisfile_riff_raw_strf_strhfccType_streamindex['biWidth'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 4, 4)); // width of the bitmap in pixels - //$thisfile_riff_raw_strf_strhfccType_streamindex['biHeight'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 8, 4)); // height of the bitmap in pixels. If biHeight is positive, the bitmap is a 'bottom-up' DIB and its origin is the lower left corner. If biHeight is negative, the bitmap is a 'top-down' DIB and its origin is the upper left corner - //$thisfile_riff_raw_strf_strhfccType_streamindex['biPlanes'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 12, 2)); // number of color planes on the target device. In most cases this value must be set to 1 - //$thisfile_riff_raw_strf_strhfccType_streamindex['biBitCount'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 14, 2)); // Specifies the number of bits per pixels - //$thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'] = substr($strfData, 16, 4); // - //$thisfile_riff_raw_strf_strhfccType_streamindex['biSizeImage'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 20, 4)); // size of the bitmap data section of the image (the actual pixel data, excluding BITMAPINFOHEADER and RGBQUAD structures) - //$thisfile_riff_raw_strf_strhfccType_streamindex['biXPelsPerMeter'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 24, 4)); // horizontal resolution, in pixels per metre, of the target device - //$thisfile_riff_raw_strf_strhfccType_streamindex['biYPelsPerMeter'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 28, 4)); // vertical resolution, in pixels per metre, of the target device - //$thisfile_riff_raw_strf_strhfccType_streamindex['biClrUsed'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 32, 4)); // actual number of color indices in the color table used by the bitmap. If this value is zero, the bitmap uses the maximum number of colors corresponding to the value of the biBitCount member for the compression mode specified by biCompression - //$thisfile_riff_raw_strf_strhfccType_streamindex['biClrImportant'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 36, 4)); // number of color indices that are considered important for displaying the bitmap. If this value is zero, all colors are important - + $thisfile_riff_raw_strf_strhfccType_streamindex = getid3_riff::ParseBITMAPINFOHEADER(substr($strfData, 0, 40), ($info['fileformat'] == 'riff')); +//echo '
'.print_r($thisfile_riff_raw_strf_strhfccType_streamindex, true).'
'; $thisfile_video['bits_per_sample'] = $thisfile_riff_raw_strf_strhfccType_streamindex['biBitCount']; if ($thisfile_riff_video_current['codec'] == 'DV') { @@ -659,30 +796,32 @@ class getid3_riff break; default: - $ThisFileInfo['warning'][] = 'Unhandled fccType for stream ('.$i.'): "'.$strhfccType.'"'; + $info['warning'][] = 'Unhandled fccType for stream ('.$i.'): "'.$strhfccType.'"'; break; } } } - if (isset($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']) && getid3_riff::RIFFfourccLookup($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'])) { + if (isset($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'])) { - $thisfile_riff_video_current['codec'] = getid3_riff::RIFFfourccLookup($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']); - $thisfile_video['codec'] = $thisfile_riff_video_current['codec']; - $thisfile_video['fourcc'] = $thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']; + $thisfile_video['fourcc'] = $thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']; + if (getid3_riff::RIFFfourccLookup($thisfile_video['fourcc'])) { + $thisfile_riff_video_current['codec'] = getid3_riff::RIFFfourccLookup($thisfile_video['fourcc']); + $thisfile_video['codec'] = $thisfile_riff_video_current['codec']; + } switch ($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']) { case 'HFYU': // Huffman Lossless Codec case 'IRAW': // Intel YUV Uncompressed case 'YUY2': // Uncompressed YUV 4:2:2 $thisfile_video['lossless'] = true; - $thisfile_video['bits_per_sample'] = 24; + //$thisfile_video['bits_per_sample'] = 24; break; default: $thisfile_video['lossless'] = false; - $thisfile_video['bits_per_sample'] = 24; + //$thisfile_video['bits_per_sample'] = 24; break; } @@ -696,26 +835,26 @@ class getid3_riff $thisfile_audio['bitrate_mode'] = 'cbr'; $thisfile_audio_dataformat = 'cda'; $thisfile_audio['lossless'] = true; - unset($ThisFileInfo['mime_type']); + unset($info['mime_type']); - $thisfile_avdataoffset = 44; + $info['avdataoffset'] = 44; if (isset($thisfile_riff['CDDA']['fmt '][0]['data'])) { // shortcut $thisfile_riff_CDDA_fmt_0 = &$thisfile_riff['CDDA']['fmt '][0]; - $thisfile_riff_CDDA_fmt_0['unknown1'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 0, 2)); - $thisfile_riff_CDDA_fmt_0['track_num'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 2, 2)); - $thisfile_riff_CDDA_fmt_0['disc_id'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 4, 4)); - $thisfile_riff_CDDA_fmt_0['start_offset_frame'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 8, 4)); - $thisfile_riff_CDDA_fmt_0['playtime_frames'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 12, 4)); - $thisfile_riff_CDDA_fmt_0['unknown6'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 16, 4)); - $thisfile_riff_CDDA_fmt_0['unknown7'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 20, 4)); + $thisfile_riff_CDDA_fmt_0['unknown1'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 0, 2)); + $thisfile_riff_CDDA_fmt_0['track_num'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 2, 2)); + $thisfile_riff_CDDA_fmt_0['disc_id'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 4, 4)); + $thisfile_riff_CDDA_fmt_0['start_offset_frame'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 8, 4)); + $thisfile_riff_CDDA_fmt_0['playtime_frames'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 12, 4)); + $thisfile_riff_CDDA_fmt_0['unknown6'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 16, 4)); + $thisfile_riff_CDDA_fmt_0['unknown7'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 20, 4)); $thisfile_riff_CDDA_fmt_0['start_offset_seconds'] = (float) $thisfile_riff_CDDA_fmt_0['start_offset_frame'] / 75; $thisfile_riff_CDDA_fmt_0['playtime_seconds'] = (float) $thisfile_riff_CDDA_fmt_0['playtime_frames'] / 75; - $ThisFileInfo['comments']['track'] = $thisfile_riff_CDDA_fmt_0['track_num']; - $ThisFileInfo['playtime_seconds'] = $thisfile_riff_CDDA_fmt_0['playtime_seconds']; + $info['comments']['track'] = $thisfile_riff_CDDA_fmt_0['track_num']; + $info['playtime_seconds'] = $thisfile_riff_CDDA_fmt_0['playtime_seconds']; // hardcoded data for CD-audio $thisfile_audio['sample_rate'] = 44100; @@ -732,19 +871,19 @@ class getid3_riff $thisfile_audio['bitrate_mode'] = 'cbr'; $thisfile_audio_dataformat = 'aiff'; $thisfile_audio['lossless'] = true; - $ThisFileInfo['mime_type'] = 'audio/x-aiff'; + $info['mime_type'] = 'audio/x-aiff'; if (isset($thisfile_riff[$RIFFsubtype]['SSND'][0]['offset'])) { - $thisfile_avdataoffset = $thisfile_riff[$RIFFsubtype]['SSND'][0]['offset'] + 8; - $thisfile_avdataend = $thisfile_avdataoffset + $thisfile_riff[$RIFFsubtype]['SSND'][0]['size']; - if ($thisfile_avdataend > $ThisFileInfo['filesize']) { - if (($thisfile_avdataend == ($ThisFileInfo['filesize'] + 1)) && (($ThisFileInfo['filesize'] % 2) == 1)) { + $info['avdataoffset'] = $thisfile_riff[$RIFFsubtype]['SSND'][0]['offset'] + 8; + $info['avdataend'] = $info['avdataoffset'] + $thisfile_riff[$RIFFsubtype]['SSND'][0]['size']; + if ($info['avdataend'] > $info['filesize']) { + if (($info['avdataend'] == ($info['filesize'] + 1)) && (($info['filesize'] % 2) == 1)) { // structures rounded to 2-byte boundary, but dumb encoders // forget to pad end of file to make this actually work } else { - $ThisFileInfo['warning'][] = 'Probable truncated AIFF file: expecting '.$thisfile_riff[$RIFFsubtype]['SSND'][0]['size'].' bytes of audio data, only '.($ThisFileInfo['filesize'] - $thisfile_avdataoffset).' bytes found'; + $info['warning'][] = 'Probable truncated AIFF file: expecting '.$thisfile_riff[$RIFFsubtype]['SSND'][0]['size'].' bytes of audio data, only '.($info['filesize'] - $info['avdataoffset']).' bytes found'; } - $thisfile_avdataend = $ThisFileInfo['filesize']; + $info['avdataend'] = $info['filesize']; } } @@ -799,28 +938,28 @@ class getid3_riff } $thisfile_audio['sample_rate'] = $thisfile_riff_audio['sample_rate']; if ($thisfile_audio['sample_rate'] == 0) { - $ThisFileInfo['error'][] = 'Corrupted AIFF file: sample_rate == zero'; + $info['error'][] = 'Corrupted AIFF file: sample_rate == zero'; return false; } - $ThisFileInfo['playtime_seconds'] = $thisfile_riff_audio['total_samples'] / $thisfile_audio['sample_rate']; + $info['playtime_seconds'] = $thisfile_riff_audio['total_samples'] / $thisfile_audio['sample_rate']; } if (isset($thisfile_riff[$RIFFsubtype]['COMT'])) { $offset = 0; - $CommentCount = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), false); + $CommentCount = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), false); $offset += 2; for ($i = 0; $i < $CommentCount; $i++) { - $ThisFileInfo['comments_raw'][$i]['timestamp'] = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 4), false); + $info['comments_raw'][$i]['timestamp'] = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 4), false); $offset += 4; - $ThisFileInfo['comments_raw'][$i]['marker_id'] = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), true); + $info['comments_raw'][$i]['marker_id'] = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), true); $offset += 2; - $CommentLength = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), false); + $CommentLength = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), false); $offset += 2; - $ThisFileInfo['comments_raw'][$i]['comment'] = substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, $CommentLength); + $info['comments_raw'][$i]['comment'] = substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, $CommentLength); $offset += $CommentLength; - $ThisFileInfo['comments_raw'][$i]['timestamp_unix'] = getid3_lib::DateMac2Unix($ThisFileInfo['comments_raw'][$i]['timestamp']); - $thisfile_riff['comments']['comment'][] = $ThisFileInfo['comments_raw'][$i]['comment']; + $info['comments_raw'][$i]['timestamp_unix'] = getid3_lib::DateMac2Unix($info['comments_raw'][$i]['timestamp']); + $thisfile_riff['comments']['comment'][] = $info['comments_raw'][$i]['comment']; } } @@ -830,6 +969,18 @@ class getid3_riff $thisfile_riff['comments'][$value][] = $thisfile_riff[$RIFFsubtype][$key][0]['data']; } } + + if (isset($thisfile_riff[$RIFFsubtype]['ID3 '])) { + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true); + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_id3v2 = new getid3_id3v2($getid3_temp); + $getid3_id3v2->StartingOffset = $thisfile_riff[$RIFFsubtype]['ID3 '][0]['offset'] + 8; + if ($thisfile_riff[$RIFFsubtype]['ID3 '][0]['valid'] = $getid3_id3v2->Analyze()) { + $info['id3v2'] = $getid3_temp->info['id3v2']; + } + unset($getid3_temp, $getid3_id3v2); + } break; case '8SVX': @@ -837,13 +988,13 @@ class getid3_riff $thisfile_audio_dataformat = '8svx'; $thisfile_audio['bits_per_sample'] = 8; $thisfile_audio['channels'] = 1; // overridden below, if need be - $ThisFileInfo['mime_type'] = 'audio/x-aiff'; + $info['mime_type'] = 'audio/x-aiff'; if (isset($thisfile_riff[$RIFFsubtype]['BODY'][0]['offset'])) { - $thisfile_avdataoffset = $thisfile_riff[$RIFFsubtype]['BODY'][0]['offset'] + 8; - $thisfile_avdataend = $thisfile_avdataoffset + $thisfile_riff[$RIFFsubtype]['BODY'][0]['size']; - if ($thisfile_avdataend > $ThisFileInfo['filesize']) { - $ThisFileInfo['warning'][] = 'Probable truncated AIFF file: expecting '.$thisfile_riff[$RIFFsubtype]['BODY'][0]['size'].' bytes of audio data, only '.($ThisFileInfo['filesize'] - $thisfile_avdataoffset).' bytes found'; + $info['avdataoffset'] = $thisfile_riff[$RIFFsubtype]['BODY'][0]['offset'] + 8; + $info['avdataend'] = $info['avdataoffset'] + $thisfile_riff[$RIFFsubtype]['BODY'][0]['size']; + if ($info['avdataend'] > $info['filesize']) { + $info['warning'][] = 'Probable truncated AIFF file: expecting '.$thisfile_riff[$RIFFsubtype]['BODY'][0]['size'].' bytes of audio data, only '.($info['filesize'] - $info['avdataoffset']).' bytes found'; } } @@ -865,17 +1016,17 @@ class getid3_riff case 0: $thisfile_audio['codec'] = 'Pulse Code Modulation (PCM)'; $thisfile_audio['lossless'] = true; - $ActualBitsPerSample = 8; + $ActualBitsPerSample = 8; break; case 1: $thisfile_audio['codec'] = 'Fibonacci-delta encoding'; $thisfile_audio['lossless'] = false; - $ActualBitsPerSample = 4; + $ActualBitsPerSample = 4; break; default: - $ThisFileInfo['warning'][] = 'Unexpected sCompression value in 8SVX.VHDR chunk - expecting 0 or 1, found "'.sCompression.'"'; + $info['warning'][] = 'Unexpected sCompression value in 8SVX.VHDR chunk - expecting 0 or 1, found "'.sCompression.'"'; break; } } @@ -893,7 +1044,7 @@ class getid3_riff break; default: - $ThisFileInfo['warning'][] = 'Unexpected value in 8SVX.CHAN chunk - expecting 2 or 4 or 6, found "'.$ChannelsIndex.'"'; + $info['warning'][] = 'Unexpected value in 8SVX.CHAN chunk - expecting 2 or 4 or 6, found "'.$ChannelsIndex.'"'; break; } @@ -908,41 +1059,41 @@ class getid3_riff $thisfile_audio['bitrate'] = $thisfile_audio['sample_rate'] * $ActualBitsPerSample * $thisfile_audio['channels']; if (!empty($thisfile_audio['bitrate'])) { - $ThisFileInfo['playtime_seconds'] = ($thisfile_avdataend - $thisfile_avdataoffset) / ($thisfile_audio['bitrate'] / 8); + $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) / ($thisfile_audio['bitrate'] / 8); } break; case 'CDXA': - $ThisFileInfo['mime_type'] = 'video/mpeg'; + $info['mime_type'] = 'video/mpeg'; if (!empty($thisfile_riff['CDXA']['data'][0]['size'])) { - $GETID3_ERRORARRAY = &$ThisFileInfo['warning']; if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.mpeg.php', __FILE__, false)) { - $dummy = $ThisFileInfo; - $dummy['error'] = array(); - $mpeg_scanner = new getid3_mpeg($fd, $dummy); - if (empty($dummy['error'])) { - $ThisFileInfo['audio'] = $dummy['audio']; - $ThisFileInfo['video'] = $dummy['video']; - $ThisFileInfo['mpeg'] = $dummy['mpeg']; - $ThisFileInfo['warning'] = $dummy['warning']; + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_mpeg = new getid3_mpeg($getid3_temp); + $getid3_mpeg->Analyze(); + if (empty($getid3_temp->info['error'])) { + $info['audio'] = $getid3_temp->info['audio']; + $info['video'] = $getid3_temp->info['video']; + $info['mpeg'] = $getid3_temp->info['mpeg']; + $info['warning'] = $getid3_temp->info['warning']; } - unset($mpeg_scanner); + unset($getid3_temp, $getid3_mpeg); } } break; default: - $ThisFileInfo['error'][] = 'Unknown RIFF type: expecting one of (WAVE|RMP3|AVI |CDDA|AIFF|AIFC|8SVX|CDXA), found "'.$RIFFsubtype.'" instead'; - unset($ThisFileInfo['fileformat']); + $info['error'][] = 'Unknown RIFF type: expecting one of (WAVE|RMP3|AVI |CDDA|AIFF|AIFC|8SVX|CDXA), found "'.$RIFFsubtype.'" instead'; + unset($info['fileformat']); break; } - if (@$thisfile_riff_raw['fmt ']['wFormatTag'] == 1) { + if (isset($thisfile_riff_raw['fmt ']['wFormatTag']) && ($thisfile_riff_raw['fmt ']['wFormatTag'] == 1)) { // http://www.mega-nerd.com/erikd/Blog/Windiots/dts.html - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $FirstFourBytes = fread($fd, 4); + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $FirstFourBytes = fread($this->getid3->fp, 4); if (preg_match('/^\xFF\x1F\x00\xE8/s', $FirstFourBytes)) { // DTSWAV $thisfile_audio_dataformat = 'dts'; @@ -959,46 +1110,52 @@ class getid3_riff if (isset($thisfile_riff_WAVE['INFO']) && is_array($thisfile_riff_WAVE['INFO'])) { $this->RIFFcommentsParse($thisfile_riff_WAVE['INFO'], $thisfile_riff['comments']); } - - if (empty($thisfile_audio['encoder']) && !empty($ThisFileInfo['mpeg']['audio']['LAME']['short_version'])) { - $thisfile_audio['encoder'] = $ThisFileInfo['mpeg']['audio']['LAME']['short_version']; + if (isset($thisfile_riff['AVI ']['INFO']) && is_array($thisfile_riff['AVI ']['INFO'])) { + $this->RIFFcommentsParse($thisfile_riff['AVI ']['INFO'], $thisfile_riff['comments']); } - if (!isset($ThisFileInfo['playtime_seconds'])) { - $ThisFileInfo['playtime_seconds'] = 0; - } - if (isset($thisfile_riff_raw['avih']['dwTotalFrames']) && isset($thisfile_riff_raw['avih']['dwMicroSecPerFrame'])) { - $ThisFileInfo['playtime_seconds'] = $thisfile_riff_raw['avih']['dwTotalFrames'] * ($thisfile_riff_raw['avih']['dwMicroSecPerFrame'] / 1000000); + if (empty($thisfile_audio['encoder']) && !empty($info['mpeg']['audio']['LAME']['short_version'])) { + $thisfile_audio['encoder'] = $info['mpeg']['audio']['LAME']['short_version']; } - if ($ThisFileInfo['playtime_seconds'] > 0) { + if (!isset($info['playtime_seconds'])) { + $info['playtime_seconds'] = 0; + } + if (isset($thisfile_riff_raw['strh'][0]['dwLength']) && isset($thisfile_riff_raw['avih']['dwMicroSecPerFrame'])) { + // needed for >2GB AVIs where 'avih' chunk only lists number of frames in that chunk, not entire movie + $info['playtime_seconds'] = $thisfile_riff_raw['strh'][0]['dwLength'] * ($thisfile_riff_raw['avih']['dwMicroSecPerFrame'] / 1000000); + } elseif (isset($thisfile_riff_raw['avih']['dwTotalFrames']) && isset($thisfile_riff_raw['avih']['dwMicroSecPerFrame'])) { + $info['playtime_seconds'] = $thisfile_riff_raw['avih']['dwTotalFrames'] * ($thisfile_riff_raw['avih']['dwMicroSecPerFrame'] / 1000000); + } + + if ($info['playtime_seconds'] > 0) { if (isset($thisfile_riff_audio) && isset($thisfile_riff_video)) { - if (!isset($ThisFileInfo['bitrate'])) { - $ThisFileInfo['bitrate'] = ((($thisfile_avdataend - $thisfile_avdataoffset) / $ThisFileInfo['playtime_seconds']) * 8); + if (!isset($info['bitrate'])) { + $info['bitrate'] = ((($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8); } } elseif (isset($thisfile_riff_audio) && !isset($thisfile_riff_video)) { if (!isset($thisfile_audio['bitrate'])) { - $thisfile_audio['bitrate'] = ((($thisfile_avdataend - $thisfile_avdataoffset) / $ThisFileInfo['playtime_seconds']) * 8); + $thisfile_audio['bitrate'] = ((($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8); } } elseif (!isset($thisfile_riff_audio) && isset($thisfile_riff_video)) { if (!isset($thisfile_video['bitrate'])) { - $thisfile_video['bitrate'] = ((($thisfile_avdataend - $thisfile_avdataoffset) / $ThisFileInfo['playtime_seconds']) * 8); + $thisfile_video['bitrate'] = ((($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8); } } } - if (isset($thisfile_riff_video) && isset($thisfile_audio['bitrate']) && ($thisfile_audio['bitrate'] > 0) && ($ThisFileInfo['playtime_seconds'] > 0)) { + if (isset($thisfile_riff_video) && isset($thisfile_audio['bitrate']) && ($thisfile_audio['bitrate'] > 0) && ($info['playtime_seconds'] > 0)) { - $ThisFileInfo['bitrate'] = ((($thisfile_avdataend - $thisfile_avdataoffset) / $ThisFileInfo['playtime_seconds']) * 8); + $info['bitrate'] = ((($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8); $thisfile_audio['bitrate'] = 0; - $thisfile_video['bitrate'] = $ThisFileInfo['bitrate']; + $thisfile_video['bitrate'] = $info['bitrate']; foreach ($thisfile_riff_audio as $channelnumber => $audioinfoarray) { $thisfile_video['bitrate'] -= $audioinfoarray['bitrate']; $thisfile_audio['bitrate'] += $audioinfoarray['bitrate']; @@ -1011,14 +1168,14 @@ class getid3_riff } } - if (isset($ThisFileInfo['mpeg']['audio'])) { - $thisfile_audio_dataformat = 'mp'.$ThisFileInfo['mpeg']['audio']['layer']; - $thisfile_audio['sample_rate'] = $ThisFileInfo['mpeg']['audio']['sample_rate']; - $thisfile_audio['channels'] = $ThisFileInfo['mpeg']['audio']['channels']; - $thisfile_audio['bitrate'] = $ThisFileInfo['mpeg']['audio']['bitrate']; - $thisfile_audio['bitrate_mode'] = strtolower($ThisFileInfo['mpeg']['audio']['bitrate_mode']); - if (!empty($ThisFileInfo['mpeg']['audio']['codec'])) { - $thisfile_audio['codec'] = $ThisFileInfo['mpeg']['audio']['codec'].' '.$thisfile_audio['codec']; + if (isset($info['mpeg']['audio'])) { + $thisfile_audio_dataformat = 'mp'.$info['mpeg']['audio']['layer']; + $thisfile_audio['sample_rate'] = $info['mpeg']['audio']['sample_rate']; + $thisfile_audio['channels'] = $info['mpeg']['audio']['channels']; + $thisfile_audio['bitrate'] = $info['mpeg']['audio']['bitrate']; + $thisfile_audio['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); + if (!empty($info['mpeg']['audio']['codec'])) { + $thisfile_audio['codec'] = $info['mpeg']['audio']['codec'].' '.$thisfile_audio['codec']; } if (!empty($thisfile_audio['streams'])) { foreach ($thisfile_audio['streams'] as $streamnumber => $streamdata) { @@ -1031,7 +1188,9 @@ class getid3_riff } } } - $thisfile_audio['encoder_options'] = getid3_mp3::GuessEncoderOptions($ThisFileInfo); + $getid3_mp3 = new getid3_mp3($this->getid3); + $thisfile_audio['encoder_options'] = $getid3_mp3->GuessEncoderOptions(); + unset($getid3_mp3); } @@ -1062,7 +1221,7 @@ class getid3_riff } - function RIFFcommentsParse(&$RIFFinfoArray, &$CommentsTargetArray) { + static function RIFFcommentsParse(&$RIFFinfoArray, &$CommentsTargetArray) { $RIFFinfoKeyLookup = array( 'IARL'=>'archivallocation', 'IART'=>'artist', @@ -1108,7 +1267,11 @@ class getid3_riff if (isset($RIFFinfoArray[$key])) { foreach ($RIFFinfoArray[$key] as $commentid => $commentdata) { if (trim($commentdata['data']) != '') { - @$CommentsTargetArray[$value][] = trim($commentdata['data']); + if (isset($CommentsTargetArray[$value])) { + $CommentsTargetArray[$value][] = trim($commentdata['data']); + } else { + $CommentsTargetArray[$value] = array(trim($commentdata['data'])); + } } } } @@ -1116,29 +1279,39 @@ class getid3_riff return true; } - function ParseRIFF(&$fd, $startoffset, $maxoffset, &$ThisFileInfo) { - $maxoffset = min($maxoffset, $ThisFileInfo['avdataend']); + function ParseRIFF($startoffset, $maxoffset) { + $info = &$this->getid3->info; + + $maxoffset = min($maxoffset, $info['avdataend']); $RIFFchunk = false; $FoundAllChunksWeNeed = false; - if (($startoffset < 0) || ($startoffset >= pow(2, 31))) { - $ThisFileInfo['warning'][] = 'Unable to ParseRIFF() at '.$startoffset.' because beyond 2GB limit of PHP filesystem functions'; + if (($startoffset < 0) || !getid3_lib::intValueSupported($startoffset)) { + $info['warning'][] = 'Unable to ParseRIFF() at '.$startoffset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions'; return false; } - fseek($fd, $startoffset, SEEK_SET); + $max_usable_offset = min(PHP_INT_MAX - 1024, $maxoffset); + if ($maxoffset > $max_usable_offset) { + $info['warning'][] = 'ParseRIFF() may return incomplete data for chunk starting at '.$startoffset.' because beyond it extends to '.$maxoffset.', which is beyond the '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions'; + } + fseek($this->getid3->fp, $startoffset, SEEK_SET); - while (ftell($fd) < $maxoffset) { - $chunkname = fread($fd, 4); + while (ftell($this->getid3->fp) < $max_usable_offset) { + $chunknamesize = fread($this->getid3->fp, 8); + $chunkname = substr($chunknamesize, 0, 4); + $chunksize = $this->EitherEndian2Int(substr($chunknamesize, 4, 4)); if (strlen($chunkname) < 4) { - $ThisFileInfo['error'][] = 'Expecting chunk name at offset '.(ftell($fd) - 4).' but found nothing. Aborting RIFF parsing.'; + $info['error'][] = 'Expecting chunk name at offset '.(ftell($this->getid3->fp) - 4).' but found nothing. Aborting RIFF parsing.'; break; } - - $chunksize = getid3_riff::EitherEndian2Int($ThisFileInfo, fread($fd, 4)); if ($chunksize == 0) { - $ThisFileInfo['warning'][] = 'Chunk size at offset '.(ftell($fd) - 4).' is zero. Aborting RIFF parsing.'; - continue; + if ($chunkname == 'JUNK') { + // we'll allow zero-size JUNK frames + } else { + $info['warning'][] = 'Chunk size at offset '.(ftell($this->getid3->fp) - 4).' is zero. Aborting RIFF parsing.'; + break; + } } if (($chunksize % 2) != 0) { // all structures are packed on word boundaries @@ -1147,9 +1320,9 @@ class getid3_riff switch ($chunkname) { case 'LIST': - $listname = fread($fd, 4); - if (eregi('^(movi|rec )$', $listname)) { - $RIFFchunk[$listname]['offset'] = ftell($fd) - 4; + $listname = fread($this->getid3->fp, 4); + if (preg_match('#^(movi|rec )$#i', $listname)) { + $RIFFchunk[$listname]['offset'] = ftell($this->getid3->fp) - 4; $RIFFchunk[$listname]['size'] = $chunksize; if ($FoundAllChunksWeNeed) { @@ -1158,8 +1331,8 @@ class getid3_riff } else { - $WhereWeWere = ftell($fd); - $AudioChunkHeader = fread($fd, 12); + $WhereWeWere = ftell($this->getid3->fp); + $AudioChunkHeader = fread($this->getid3->fp, 12); $AudioChunkStreamNum = substr($AudioChunkHeader, 0, 2); $AudioChunkStreamType = substr($AudioChunkHeader, 2, 2); $AudioChunkSize = getid3_lib::LittleEndian2Int(substr($AudioChunkHeader, 4, 4)); @@ -1169,40 +1342,45 @@ class getid3_riff if (preg_match('/^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\xEB]/s', $FirstFourBytes)) { // MP3 if (getid3_mp3::MPEGaudioHeaderBytesValid($FirstFourBytes)) { - $dummy = $ThisFileInfo; - $dummy['avdataoffset'] = ftell($fd) - 4; - $dummy['avdataend'] = ftell($fd) + $AudioChunkSize; - getid3_mp3::getOnlyMPEGaudioInfo($fd, $dummy, $dummy['avdataoffset'], false); - if (isset($dummy['mpeg']['audio'])) { - $ThisFileInfo = $dummy; - $ThisFileInfo['audio']['dataformat'] = 'mp'.$ThisFileInfo['mpeg']['audio']['layer']; - $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['mpeg']['audio']['sample_rate']; - $ThisFileInfo['audio']['channels'] = $ThisFileInfo['mpeg']['audio']['channels']; - $ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['mpeg']['audio']['bitrate']; - $ThisFileInfo['bitrate'] = $ThisFileInfo['audio']['bitrate']; - $ThisFileInfo['audio']['bitrate_mode'] = strtolower($ThisFileInfo['mpeg']['audio']['bitrate_mode']); + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = ftell($this->getid3->fp) - 4; + $getid3_temp->info['avdataend'] = ftell($this->getid3->fp) + $AudioChunkSize; + $getid3_mp3 = new getid3_mp3($getid3_temp); + $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false); + if (isset($getid3_temp->info['mpeg']['audio'])) { + $info['mpeg']['audio'] = $getid3_temp->info['mpeg']['audio']; + $info['audio'] = $getid3_temp->info['audio']; + $info['audio']['dataformat'] = 'mp'.$info['mpeg']['audio']['layer']; + $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; + $info['audio']['channels'] = $info['mpeg']['audio']['channels']; + $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; + $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); + //$info['bitrate'] = $info['audio']['bitrate']; } - unset($dummy); + unset($getid3_temp, $getid3_mp3); } } elseif (preg_match('/^\x0B\x77/s', $FirstFourBytes)) { // AC3 - $GETID3_ERRORARRAY = &$ThisFileInfo['warning']; if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, false)) { - - $dummy = $ThisFileInfo; - $dummy['avdataoffset'] = ftell($fd) - 4; - $dummy['avdataend'] = ftell($fd) + $AudioChunkSize; - $dummy['error'] = array(); - $ac3_tag = new getid3_ac3($fd, $dummy); - if (empty($dummy['error'])) { - $ThisFileInfo['audio'] = $dummy['audio']; - $ThisFileInfo['ac3'] = $dummy['ac3']; - $ThisFileInfo['warning'] = $dummy['warning']; + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = ftell($this->getid3->fp) - 4; + $getid3_temp->info['avdataend'] = ftell($this->getid3->fp) + $AudioChunkSize; + $getid3_ac3 = new getid3_ac3($getid3_temp); + $getid3_ac3->Analyze(); + if (empty($getid3_temp->info['error'])) { + $info['audio'] = $getid3_temp->info['audio']; + $info['ac3'] = $getid3_temp->info['ac3']; + if (!empty($getid3_temp->info['warning'])) { + foreach ($getid3_temp->info['warning'] as $key => $value) { + $info['warning'][] = $value; + } + } } - unset($ac3_tag); - + unset($getid3_temp, $getid3_ac3); } } @@ -1210,23 +1388,23 @@ class getid3_riff } $FoundAllChunksWeNeed = true; - fseek($fd, $WhereWeWere, SEEK_SET); + fseek($this->getid3->fp, $WhereWeWere, SEEK_SET); } - fseek($fd, $chunksize - 4, SEEK_CUR); + fseek($this->getid3->fp, $chunksize - 4, SEEK_CUR); - //} elseif (ereg('^[0-9]{2}(wb|pc|dc|db)$', $listname)) { - // + //} elseif (preg_match('#^[0-9]{2}(wb|pc|dc|db)$#i', $listname)) { + // // // data chunk, ignore - // + // } else { if (!isset($RIFFchunk[$listname])) { $RIFFchunk[$listname] = array(); } $LISTchunkParent = $listname; - $LISTchunkMaxOffset = ftell($fd) - 4 + $chunksize; - if ($parsedChunk = getid3_riff::ParseRIFF($fd, ftell($fd), ftell($fd) + $chunksize - 4, $ThisFileInfo)) { + $LISTchunkMaxOffset = ftell($this->getid3->fp) - 4 + $chunksize; + if ($parsedChunk = $this->ParseRIFF(ftell($this->getid3->fp), ftell($this->getid3->fp) + $chunksize - 4)) { $RIFFchunk[$listname] = array_merge_recursive($RIFFchunk[$listname], $parsedChunk); } @@ -1234,63 +1412,61 @@ class getid3_riff break; default: - if (eregi('^[0-9]{2}(wb|pc|dc|db)$', $chunkname)) { - $nextoffset = ftell($fd) + $chunksize; - if (($nextoffset < 0) || ($nextoffset >= pow(2, 31))) { - $ThisFileInfo['warning'][] = 'Unable to parse chunk at offset '.$nextoffset.' because beyond 2GB limit of PHP filesystem functions'; + if (preg_match('#^[0-9]{2}(wb|pc|dc|db)$#', $chunkname)) { + $nextoffset = ftell($this->getid3->fp) + $chunksize; + if (($nextoffset < 0) || !getid3_lib::intValueSupported($nextoffset)) { + $info['warning'][] = 'Unable to parse chunk at offset '.$nextoffset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions'; break 2; } - fseek($fd, $nextoffset, SEEK_SET); + fseek($this->getid3->fp, $nextoffset, SEEK_SET); break; } $thisindex = 0; if (isset($RIFFchunk[$chunkname]) && is_array($RIFFchunk[$chunkname])) { $thisindex = count($RIFFchunk[$chunkname]); } - $RIFFchunk[$chunkname][$thisindex]['offset'] = ftell($fd) - 8; + $RIFFchunk[$chunkname][$thisindex]['offset'] = ftell($this->getid3->fp) - 8; $RIFFchunk[$chunkname][$thisindex]['size'] = $chunksize; switch ($chunkname) { case 'data': - $ThisFileInfo['avdataoffset'] = ftell($fd); - $ThisFileInfo['avdataend'] = $ThisFileInfo['avdataoffset'] + $chunksize; + $info['avdataoffset'] = ftell($this->getid3->fp); + $info['avdataend'] = $info['avdataoffset'] + $chunksize; - $RIFFdataChunkContentsTest = fread($fd, 36); + $RIFFdataChunkContentsTest = fread($this->getid3->fp, 36); if ((strlen($RIFFdataChunkContentsTest) > 0) && preg_match('/^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\xEB]/s', substr($RIFFdataChunkContentsTest, 0, 4))) { // Probably is MP3 data if (getid3_mp3::MPEGaudioHeaderBytesValid(substr($RIFFdataChunkContentsTest, 0, 4))) { - - // copy info array - $dummy = $ThisFileInfo; - - getid3_mp3::getOnlyMPEGaudioInfo($fd, $dummy, $RIFFchunk[$chunkname][$thisindex]['offset'], false); - - // use dummy array unless error - if (empty($dummy['error'])) { - $ThisFileInfo = $dummy; + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; + $getid3_temp->info['avdataend'] = $RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size']; + $getid3_mp3 = new getid3_mp3($getid3_temp); + $getid3_mp3->getOnlyMPEGaudioInfo($RIFFchunk[$chunkname][$thisindex]['offset'], false); + if (empty($getid3_temp->info['error'])) { + $info['mpeg'] = $getid3_temp->info['mpeg']; + $info['audio'] = $getid3_temp->info['audio']; } + unset($getid3_temp, $getid3_mp3); } } elseif ((strlen($RIFFdataChunkContentsTest) > 0) && (substr($RIFFdataChunkContentsTest, 0, 2) == "\x0B\x77")) { // This is probably AC-3 data - $GETID3_ERRORARRAY = &$ThisFileInfo['warning']; if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, false)) { - - $dummy = $ThisFileInfo; - $dummy['avdataoffset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; - $dummy['avdataend'] = $dummy['avdataoffset'] + $RIFFchunk[$chunkname][$thisindex]['size']; - $dummy['error'] = array(); - - $ac3_tag = new getid3_ac3($fd, $dummy); - if (empty($dummy['error'])) { - $ThisFileInfo['audio'] = $dummy['audio']; - $ThisFileInfo['ac3'] = $dummy['ac3']; - $ThisFileInfo['warning'] = $dummy['warning']; + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; + $getid3_temp->info['avdataend'] = $RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size']; + $getid3_ac3 = new getid3_ac3($getid3_temp); + $getid3_ac3->Analyze(); + if (empty($getid3_temp->info['error'])) { + $info['audio'] = $getid3_temp->info['audio']; + $info['ac3'] = $getid3_temp->info['ac3']; + $info['warning'] = $getid3_temp->info['warning']; } - unset($ac3_tag); - + unset($getid3_temp, $getid3_ac3); } } elseif ((strlen($RIFFdataChunkContentsTest) > 0) && (substr($RIFFdataChunkContentsTest, 8, 2) == "\x77\x0B")) { @@ -1299,37 +1475,38 @@ class getid3_riff // AC-3 content, but not encoded in same format as normal AC-3 file // For one thing, byte order is swapped - $GETID3_ERRORARRAY = &$ThisFileInfo['warning']; if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, false)) { // ok to use tmpfile here - only 56 bytes - if ($fd_temp = tmpfile()) { + if ($RIFFtempfilename = tempnam(GETID3_TEMP_DIR, 'id3')) { + if ($fd_temp = fopen($RIFFtempfilename, 'wb')) { + for ($i = 0; $i < 28; $i += 2) { + // swap byte order + fwrite($fd_temp, substr($RIFFdataChunkContentsTest, 8 + $i + 1, 1)); + fwrite($fd_temp, substr($RIFFdataChunkContentsTest, 8 + $i + 0, 1)); + } + fclose($fd_temp); - for ($i = 0; $i < 28; $i += 2) { - // swap byte order - fwrite($fd_temp, substr($RIFFdataChunkContentsTest, 8 + $i + 1, 1)); - fwrite($fd_temp, substr($RIFFdataChunkContentsTest, 8 + $i + 0, 1)); - } - - $dummy = $ThisFileInfo; - $dummy['avdataoffset'] = 0; - $dummy['avdataend'] = 20; - $dummy['error'] = array(); - $ac3_tag = new getid3_ac3($fd_temp, $dummy); - fclose($fd_temp); - if (empty($dummy['error'])) { - $ThisFileInfo['audio'] = $dummy['audio']; - $ThisFileInfo['ac3'] = $dummy['ac3']; - $ThisFileInfo['warning'] = $dummy['warning']; + $getid3_temp = new getID3(); + $getid3_temp->openfile($RIFFtempfilename); + $getid3_temp->info['avdataend'] = 20; + $getid3_ac3 = new getid3_ac3($getid3_temp); + $getid3_ac3->Analyze(); + if (empty($getid3_temp->info['error'])) { + $info['audio'] = $getid3_temp->info['audio']; + $info['ac3'] = $getid3_temp->info['ac3']; + $info['warning'] = $getid3_temp->info['warning']; + } else { + $info['error'][] = 'Error parsing Dolby Digital WAV (AC3-in-RIFF): '.implode(';', $getid3_temp->info['error']); + } + unset($getid3_ac3, $getid3_temp); } else { - $ThisFileInfo['error'][] = 'Errors parsing DolbyDigital WAV: '.explode(';', $dummy['error']); + $info['error'][] = 'Error parsing Dolby Digital WAV (AC3-in-RIFF): failed to write temp file'; } - unset($ac3_tag); + unlink($RIFFtempfilename); } else { - - $ThisFileInfo['error'][] = 'Could not create temporary file to analyze DolbyDigital WAV'; - + $info['error'][] = 'Error parsing Dolby Digital WAV (AC3-in-RIFF): failed to write temp file'; } } @@ -1337,9 +1514,9 @@ class getid3_riff } elseif ((strlen($RIFFdataChunkContentsTest) > 0) && (substr($RIFFdataChunkContentsTest, 0, 4) == 'wvpk')) { // This is WavPack data - $ThisFileInfo['wavpack']['offset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; - $ThisFileInfo['wavpack']['size'] = getid3_lib::LittleEndian2Int(substr($RIFFdataChunkContentsTest, 4, 4)); - getid3_riff::RIFFparseWavPackHeader(substr($RIFFdataChunkContentsTest, 8, 28), $ThisFileInfo); + $info['wavpack']['offset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; + $info['wavpack']['size'] = getid3_lib::LittleEndian2Int(substr($RIFFdataChunkContentsTest, 4, 4)); + $this->RIFFparseWavPackHeader(substr($RIFFdataChunkContentsTest, 8, 28)); } else { @@ -1348,13 +1525,14 @@ class getid3_riff } $nextoffset = $RIFFchunk[$chunkname][$thisindex]['offset'] + 8 + $chunksize; - if (($nextoffset < 0) || ($nextoffset >= pow(2, 31))) { - $ThisFileInfo['warning'][] = 'Unable to parse chunk at offset '.$nextoffset.' because beyond 2GB limit of PHP filesystem functions'; + if (($nextoffset < 0) || !getid3_lib::intValueSupported($nextoffset)) { + $info['warning'][] = 'Unable to parse chunk at offset '.$nextoffset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions'; break 3; } - fseek($fd, $RIFFchunk[$chunkname][$thisindex]['offset'] + 8 + $chunksize, SEEK_SET); + fseek($this->getid3->fp, $RIFFchunk[$chunkname][$thisindex]['offset'] + 8 + $chunksize, SEEK_SET); break; + case 'iXML': case 'bext': case 'cart': case 'fmt ': @@ -1364,11 +1542,35 @@ class getid3_riff case 'MEXT': case 'DISP': // always read data in - $RIFFchunk[$chunkname][$thisindex]['data'] = fread($fd, $chunksize); + case 'JUNK': + // should be: never read data in + // but some programs write their version strings in a JUNK chunk (e.g. VirtualDub, AVIdemux, etc) + if ($chunksize < 1048576) { + if ($chunksize > 0) { + $RIFFchunk[$chunkname][$thisindex]['data'] = fread($this->getid3->fp, $chunksize); + if ($chunkname == 'JUNK') { + if (preg_match('#^([\\x20-\\x7F]+)#', $RIFFchunk[$chunkname][$thisindex]['data'], $matches)) { + // only keep text characters [chr(32)-chr(127)] + $info['riff']['comments']['junk'][] = trim($matches[1]); + } + // but if nothing there, ignore + // remove the key in either case + unset($RIFFchunk[$chunkname][$thisindex]['data']); + } + } + } else { + $info['warning'][] = 'chunk "'.$chunkname.'" at offset '.ftell($this->getid3->fp).' is unexpectedly larger than 1MB (claims to be '.number_format($chunksize).' bytes), skipping data'; + $nextoffset = ftell($this->getid3->fp) + $chunksize; + if (($nextoffset < 0) || !getid3_lib::intValueSupported($nextoffset)) { + $info['warning'][] = 'Unable to parse chunk at offset '.$nextoffset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions'; + break 3; + } + fseek($this->getid3->fp, $nextoffset, SEEK_SET); + } break; default: - if (!ereg('^[0-9]{2}(wb|pc|dc|db)$', $chunkname) && !empty($LISTchunkParent) && (($RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size']) <= $LISTchunkMaxOffset)) { + if (!preg_match('#^[0-9]{2}(wb|pc|dc|db)$#', $chunkname) && !empty($LISTchunkParent) && (($RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size']) <= $LISTchunkMaxOffset)) { $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['offset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['size'] = $RIFFchunk[$chunkname][$thisindex]['size']; unset($RIFFchunk[$chunkname][$thisindex]['offset']); @@ -1379,17 +1581,18 @@ class getid3_riff if (isset($RIFFchunk[$chunkname]) && empty($RIFFchunk[$chunkname])) { unset($RIFFchunk[$chunkname]); } - $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['data'] = fread($fd, $chunksize); - } elseif ($chunksize < 2048) { + $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['data'] = fread($this->getid3->fp, $chunksize); + //} elseif (in_array($chunkname, array('ID3 ')) || (($chunksize > 0) && ($chunksize < 2048))) { + } elseif (($chunksize > 0) && ($chunksize < 2048)) { // only read data in if smaller than 2kB - $RIFFchunk[$chunkname][$thisindex]['data'] = fread($fd, $chunksize); + $RIFFchunk[$chunkname][$thisindex]['data'] = fread($this->getid3->fp, $chunksize); } else { - $nextoffset = ftell($fd) + $chunksize; - if (($nextoffset < 0) || ($nextoffset >= pow(2, 31))) { - $ThisFileInfo['warning'][] = 'Unable to parse chunk at offset '.$nextoffset.' because beyond 2GB limit of PHP filesystem functions'; + $nextoffset = ftell($this->getid3->fp) + $chunksize; + if (($nextoffset < 0) || !getid3_lib::intValueSupported($nextoffset)) { + $info['warning'][] = 'Unable to parse chunk at offset '.$nextoffset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions'; break 3; } - fseek($fd, $nextoffset, SEEK_SET); + fseek($this->getid3->fp, $nextoffset, SEEK_SET); } break; } @@ -1403,11 +1606,11 @@ class getid3_riff } - function ParseRIFFdata(&$RIFFdata, &$ThisFileInfo) { + function ParseRIFFdata(&$RIFFdata) { + $info = &$this->getid3->info; if ($RIFFdata) { - - $tempfile = tempnam('*', 'getID3'); - $fp_temp = fopen($tempfile, "wb"); + $tempfile = tempnam(GETID3_TEMP_DIR, 'getID3'); + $fp_temp = fopen($tempfile, 'wb'); $RIFFdataLength = strlen($RIFFdata); $NewLengthString = getid3_lib::LittleEndian2String($RIFFdataLength, 4); for ($i = 0; $i < 4; $i++) { @@ -1416,24 +1619,32 @@ class getid3_riff fwrite($fp_temp, $RIFFdata); fclose($fp_temp); - $fp_temp = fopen($tempfile, "rb"); - $dummy = array('filesize'=>$RIFFdataLength, 'filenamepath'=>$ThisFileInfo['filenamepath'], 'tags'=>$ThisFileInfo['tags'], 'avdataoffset'=>0, 'avdataend'=>$RIFFdataLength, 'warning'=>$ThisFileInfo['warning'], 'error'=>$ThisFileInfo['error'], 'comments'=>$ThisFileInfo['comments'], 'audio'=>(isset($ThisFileInfo['audio']) ? $ThisFileInfo['audio'] : array()), 'video'=>(isset($ThisFileInfo['video']) ? $ThisFileInfo['video'] : array())); - $riff = new getid3_riff($fp_temp, $dummy); - $ThisFileInfo['riff'] = $dummy['riff']; - $ThisFileInfo['warning'] = $dummy['warning']; - $ThisFileInfo['error'] = $dummy['error']; - $ThisFileInfo['tags'] = $dummy['tags']; - $ThisFileInfo['comments'] = $dummy['comments']; - unset($riff); - fclose($fp_temp); + $getid3_temp = new getID3(); + $getid3_temp->openfile($tempfile); + $getid3_temp->info['filesize'] = $RIFFdataLength; + $getid3_temp->info['filenamepath'] = $info['filenamepath']; + $getid3_temp->info['tags'] = $info['tags']; + $getid3_temp->info['warning'] = $info['warning']; + $getid3_temp->info['error'] = $info['error']; + $getid3_temp->info['comments'] = $info['comments']; + $getid3_temp->info['audio'] = (isset($info['audio']) ? $info['audio'] : array()); + $getid3_temp->info['video'] = (isset($info['video']) ? $info['video'] : array()); + $getid3_riff = new getid3_riff($getid3_temp); + $getid3_riff->Analyze(); + + $info['riff'] = $getid3_temp->info['riff']; + $info['warning'] = $getid3_temp->info['warning']; + $info['error'] = $getid3_temp->info['error']; + $info['tags'] = $getid3_temp->info['tags']; + $info['comments'] = $getid3_temp->info['comments']; + unset($getid3_riff, $getid3_temp); unlink($tempfile); - return true; } return false; } - function RIFFparseWAVEFORMATex($WaveFormatExData) { + public static function RIFFparseWAVEFORMATex($WaveFormatExData) { // shortcut $WaveFormatEx['raw'] = array(); $WaveFormatEx_raw = &$WaveFormatEx['raw']; @@ -1458,7 +1669,7 @@ class getid3_riff } - function RIFFparseWavPackHeader($WavPackChunkData, &$ThisFileInfo) { + function RIFFparseWavPackHeader($WavPackChunkData) { // typedef struct { // char ckID [4]; // long ckSize; @@ -1470,8 +1681,9 @@ class getid3_riff // } WavpackHeader; // shortcut - $ThisFileInfo['wavpack'] = array(); - $thisfile_wavpack = &$ThisFileInfo['wavpack']; + $info = &$this->getid3->info; + $info['wavpack'] = array(); + $thisfile_wavpack = &$info['wavpack']; $thisfile_wavpack['version'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 0, 2)); if ($thisfile_wavpack['version'] >= 2) { @@ -1518,25 +1730,108 @@ class getid3_riff return true; } - function ParseBITMAPINFOHEADER($BITMAPINFOHEADER, $littleEndian=true) { - // yes it's ugly to instantiate a getid3_lib object here, suggested alternative please? - $getid3_lib = new getid3_lib(); + public static function ParseBITMAPINFOHEADER($BITMAPINFOHEADER, $littleEndian=true) { + $functionname = ($littleEndian ? 'LittleEndian2Int' : 'BigEndian2Int'); - $parsed['biSize'] = $getid3_lib->$functionname(substr($BITMAPINFOHEADER, 0, 4)); // number of bytes required by the BITMAPINFOHEADER structure - $parsed['biWidth'] = $getid3_lib->$functionname(substr($BITMAPINFOHEADER, 4, 4)); // width of the bitmap in pixels - $parsed['biHeight'] = $getid3_lib->$functionname(substr($BITMAPINFOHEADER, 8, 4)); // height of the bitmap in pixels. If biHeight is positive, the bitmap is a 'bottom-up' DIB and its origin is the lower left corner. If biHeight is negative, the bitmap is a 'top-down' DIB and its origin is the upper left corner - $parsed['biPlanes'] = $getid3_lib->$functionname(substr($BITMAPINFOHEADER, 12, 2)); // number of color planes on the target device. In most cases this value must be set to 1 - $parsed['biBitCount'] = $getid3_lib->$functionname(substr($BITMAPINFOHEADER, 14, 2)); // Specifies the number of bits per pixels + $parsed['biSize'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 0, 4)); // number of bytes required by the BITMAPINFOHEADER structure + $parsed['biWidth'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 4, 4)); // width of the bitmap in pixels + $parsed['biHeight'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 8, 4)); // height of the bitmap in pixels. If biHeight is positive, the bitmap is a 'bottom-up' DIB and its origin is the lower left corner. If biHeight is negative, the bitmap is a 'top-down' DIB and its origin is the upper left corner + $parsed['biPlanes'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 12, 2)); // number of color planes on the target device. In most cases this value must be set to 1 + $parsed['biBitCount'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 14, 2)); // Specifies the number of bits per pixels $parsed['fourcc'] = substr($BITMAPINFOHEADER, 16, 4); // compression identifier - $parsed['biSizeImage'] = $getid3_lib->$functionname(substr($BITMAPINFOHEADER, 20, 4)); // size of the bitmap data section of the image (the actual pixel data, excluding BITMAPINFOHEADER and RGBQUAD structures) - $parsed['biXPelsPerMeter'] = $getid3_lib->$functionname(substr($BITMAPINFOHEADER, 24, 4)); // horizontal resolution, in pixels per metre, of the target device - $parsed['biYPelsPerMeter'] = $getid3_lib->$functionname(substr($BITMAPINFOHEADER, 28, 4)); // vertical resolution, in pixels per metre, of the target device - $parsed['biClrUsed'] = $getid3_lib->$functionname(substr($BITMAPINFOHEADER, 32, 4)); // actual number of color indices in the color table used by the bitmap. If this value is zero, the bitmap uses the maximum number of colors corresponding to the value of the biBitCount member for the compression mode specified by biCompression - $parsed['biClrImportant'] = $getid3_lib->$functionname(substr($BITMAPINFOHEADER, 36, 4)); // number of color indices that are considered important for displaying the bitmap. If this value is zero, all colors are important + $parsed['biSizeImage'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 20, 4)); // size of the bitmap data section of the image (the actual pixel data, excluding BITMAPINFOHEADER and RGBQUAD structures) + $parsed['biXPelsPerMeter'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 24, 4)); // horizontal resolution, in pixels per metre, of the target device + $parsed['biYPelsPerMeter'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 28, 4)); // vertical resolution, in pixels per metre, of the target device + $parsed['biClrUsed'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 32, 4)); // actual number of color indices in the color table used by the bitmap. If this value is zero, the bitmap uses the maximum number of colors corresponding to the value of the biBitCount member for the compression mode specified by biCompression + $parsed['biClrImportant'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 36, 4)); // number of color indices that are considered important for displaying the bitmap. If this value is zero, all colors are important + return $parsed; } - function RIFFwFormatTagLookup($wFormatTag) { + static function ParseDIVXTAG($DIVXTAG) { + // structure from "IDivX" source, Form1.frm, by "Greg Frazier of Daemonic Software Group", email: gfrazier@icestorm.net, web: http://dsg.cjb.net/ + // source available at http://files.divx-digest.com/download/c663efe7ef8ad2e90bf4af4d3ea6188a/on0SWN2r/edit/IDivX.zip + // 'Byte Layout: '1111111111111111 + // '32 for Movie - 1 '1111111111111111 + // '28 for Author - 6 '6666666666666666 + // '4 for year - 2 '6666666666662222 + // '3 for genre - 3 '7777777777777777 + // '48 for Comments - 7 '7777777777777777 + // '1 for Rating - 4 '7777777777777777 + // '5 for Future Additions - 0 '333400000DIVXTAG + // '128 bytes total + + static $DIVXTAGgenre = array( + 0 => 'Action', + 1 => 'Action/Adventure', + 2 => 'Adventure', + 3 => 'Adult', + 4 => 'Anime', + 5 => 'Cartoon', + 6 => 'Claymation', + 7 => 'Comedy', + 8 => 'Commercial', + 9 => 'Documentary', + 10 => 'Drama', + 11 => 'Home Video', + 12 => 'Horror', + 13 => 'Infomercial', + 14 => 'Interactive', + 15 => 'Mystery', + 16 => 'Music Video', + 17 => 'Other', + 18 => 'Religion', + 19 => 'Sci Fi', + 20 => 'Thriller', + 21 => 'Western', + ); + static $DIVXTAGrating = array( + 0=>'Unrated', + 1=>'G', + 2=>'PG', + 3=>'PG-13', + 4=>'R', + 5=>'NC-17' + ); + + $parsed['title'] = trim(substr($DIVXTAG, 0, 32)); + $parsed['artist'] = trim(substr($DIVXTAG, 32, 28)); + $parsed['year'] = intval(trim(substr($DIVXTAG, 60, 4))); + $parsed['comment'] = trim(substr($DIVXTAG, 64, 48)); + $parsed['genre_id'] = intval(trim(substr($DIVXTAG, 112, 3))); + $parsed['rating_id'] = ord(substr($DIVXTAG, 115, 1)); + //$parsed['padding'] = substr($DIVXTAG, 116, 5); // 5-byte null + //$parsed['magic'] = substr($DIVXTAG, 121, 7); // "DIVXTAG" + + $parsed['genre'] = (isset($DIVXTAGgenre[$parsed['genre_id']]) ? $DIVXTAGgenre[$parsed['genre_id']] : $parsed['genre_id']); + $parsed['rating'] = (isset($DIVXTAGrating[$parsed['rating_id']]) ? $DIVXTAGrating[$parsed['rating_id']] : $parsed['rating_id']); + return $parsed; + } + + static function RIFFwaveSNDMtagLookup($tagshortname) { + $begin = __LINE__; + + /** This is not a comment! + + ©kwd keywords + ©BPM bpm + ©trt tracktitle + ©des description + ©gen category + ©fin featuredinstrument + ©LID longid + ©bex bwdescription + ©pub publisher + ©cdt cdtitle + ©alb library + ©com composer + + */ + + return getid3_lib::EmbeddedLookup($tagshortname, $begin, __LINE__, __FILE__, 'riff-sndm'); + } + + static function RIFFwFormatTagLookup($wFormatTag) { $begin = __LINE__; @@ -1707,7 +2002,7 @@ class getid3_riff } - function RIFFfourccLookup($fourcc) { + public static function RIFFfourccLookup($fourcc) { $begin = __LINE__; @@ -1811,9 +2106,12 @@ class getid3_riff ETV2 eTreppid Video ETV2 ETVC eTreppid Video ETVC FLIC Autodesk FLI/FLC Animation + FLV1 Sorenson Spark + FLV4 On2 TrueMotion VP6 FRWT Darim Vision Forward Motion JPEG (www.darvision.com) FRWU Darim Vision Forward Uncompressed (www.darvision.com) FLJP D-Vision Field Encoded Motion JPEG + FPS1 FRAPS v1 FRWA SoftLab-Nsk Forward Motion JPEG w/ alpha channel FRWD SoftLab-Nsk Forward Motion JPEG FVF1 Iterated Systems Fractal Video Frame @@ -2051,6 +2349,7 @@ class getid3_riff VLV1 VideoLogic/PURE Digital Videologic Capture VP30 On2 VP3.0 VP31 On2 VP3.1 + VP6F On2 TrueMotion VP6 VX1K Lucent VX1000S Video Codec VX2K Lucent VX2000S Video Codec VXSP Lucent VX1000SP Video Codec @@ -2098,8 +2397,8 @@ class getid3_riff } - function EitherEndian2Int(&$ThisFileInfo, $byteword, $signed=false) { - if ($ThisFileInfo['fileformat'] == 'riff') { + function EitherEndian2Int($byteword, $signed=false) { + if ($this->getid3->info['fileformat'] == 'riff') { return getid3_lib::LittleEndian2Int($byteword, $signed); } return getid3_lib::BigEndian2Int($byteword, false, $signed); diff --git a/apps/media/getID3/getid3/module.audio-video.swf.php b/3rdparty/getid3/module.audio-video.swf.php similarity index 53% rename from apps/media/getID3/getid3/module.audio-video.swf.php rename to 3rdparty/getid3/module.audio-video.swf.php index c3dbb366bc..a3d49f9506 100644 --- a/apps/media/getID3/getid3/module.audio-video.swf.php +++ b/3rdparty/getid3/module.audio-video.swf.php @@ -14,59 +14,52 @@ ///////////////////////////////////////////////////////////////// -class getid3_swf +class getid3_swf extends getid3_handler { + var $ReturnAllTagData = false; - function getid3_swf(&$fd, &$ThisFileInfo, $ReturnAllTagData=false) { -//$start_time = microtime(true); - $ThisFileInfo['fileformat'] = 'swf'; - $ThisFileInfo['video']['dataformat'] = 'swf'; + function Analyze() { + $info = &$this->getid3->info; + + $info['fileformat'] = 'swf'; + $info['video']['dataformat'] = 'swf'; // http://www.openswf.org/spec/SWFfileformat.html - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $SWFfileData = fread($fd, $ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']); // 8 + 2 + 2 + max(9) bytes NOT including Frame_Size RECT data + $SWFfileData = fread($this->getid3->fp, $info['avdataend'] - $info['avdataoffset']); // 8 + 2 + 2 + max(9) bytes NOT including Frame_Size RECT data - $ThisFileInfo['swf']['header']['signature'] = substr($SWFfileData, 0, 3); - switch ($ThisFileInfo['swf']['header']['signature']) { + $info['swf']['header']['signature'] = substr($SWFfileData, 0, 3); + switch ($info['swf']['header']['signature']) { case 'FWS': - $ThisFileInfo['swf']['header']['compressed'] = false; + $info['swf']['header']['compressed'] = false; break; case 'CWS': - $ThisFileInfo['swf']['header']['compressed'] = true; + $info['swf']['header']['compressed'] = true; break; default: - $ThisFileInfo['error'][] = 'Expecting "FWS" or "CWS" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$ThisFileInfo['swf']['header']['signature'].'"'; - unset($ThisFileInfo['swf']); - unset($ThisFileInfo['fileformat']); + $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; } - $ThisFileInfo['swf']['header']['version'] = getid3_lib::LittleEndian2Int(substr($SWFfileData, 3, 1)); - $ThisFileInfo['swf']['header']['length'] = getid3_lib::LittleEndian2Int(substr($SWFfileData, 4, 4)); - -//echo __LINE__.'='.number_format(microtime(true) - $start_time, 3).'
'; - - if ($ThisFileInfo['swf']['header']['compressed']) { + $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 { - - $ThisFileInfo['error'][] = 'Error decompressing compressed SWF data ('.strlen($SWFfileData).' bytes compressed, should be '.($ThisFileInfo['swf']['header']['length'] - 8).' bytes uncompressed)'; + $info['error'][] = 'Error decompressing compressed SWF data ('.strlen($SWFfileData).' bytes compressed, should be '.($info['swf']['header']['length'] - 8).' bytes uncompressed)'; return false; - } - } -//echo __LINE__.'='.number_format(microtime(true) - $start_time, 3).'
'; $FrameSizeBitsPerValue = (ord(substr($SWFfileData, 8, 1)) & 0xF8) >> 3; $FrameSizeDataLength = ceil((5 + (4 * $FrameSizeBitsPerValue)) / 8); @@ -75,8 +68,8 @@ class getid3_swf $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)); - $ThisFileInfo['swf']['header']['frame_width'] = getid3_lib::Bin2Dec($X2); - $ThisFileInfo['swf']['header']['frame_height'] = getid3_lib::Bin2Dec($Y2); + $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. @@ -85,16 +78,16 @@ class getid3_swf // Example: 0x000C -> 0x0C -> 12 So the frame rate is 12 fps. // Byte at (8 + $FrameSizeDataLength) is always zero and ignored - $ThisFileInfo['swf']['header']['frame_rate'] = getid3_lib::LittleEndian2Int(substr($SWFfileData, 9 + $FrameSizeDataLength, 1)); - $ThisFileInfo['swf']['header']['frame_count'] = getid3_lib::LittleEndian2Int(substr($SWFfileData, 10 + $FrameSizeDataLength, 2)); + $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)); - $ThisFileInfo['video']['frame_rate'] = $ThisFileInfo['swf']['header']['frame_rate']; - $ThisFileInfo['video']['resolution_x'] = intval(round($ThisFileInfo['swf']['header']['frame_width'] / 20)); - $ThisFileInfo['video']['resolution_y'] = intval(round($ThisFileInfo['swf']['header']['frame_height'] / 20)); - $ThisFileInfo['video']['pixel_aspect_ratio'] = (float) 1; + $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 (($ThisFileInfo['swf']['header']['frame_count'] > 0) && ($ThisFileInfo['swf']['header']['frame_rate'] > 0)) { - $ThisFileInfo['playtime_seconds'] = $ThisFileInfo['swf']['header']['frame_count'] / $ThisFileInfo['swf']['header']['frame_rate']; + 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).'
'; @@ -126,13 +119,13 @@ class getid3_swf break 2; case 9: // Set background color - //$ThisFileInfo['swf']['tags'][] = $TagData; - $ThisFileInfo['swf']['bgcolor'] = strtoupper(str_pad(dechex(getid3_lib::BigEndian2Int($TagData['data'])), 6, '0', STR_PAD_LEFT)); + //$info['swf']['tags'][] = $TagData; + $info['swf']['bgcolor'] = strtoupper(str_pad(dechex(getid3_lib::BigEndian2Int($TagData['data'])), 6, '0', STR_PAD_LEFT)); break; default: - if ($ReturnAllTagData) { - $ThisFileInfo['swf']['tags'][] = $TagData; + if ($this->ReturnAllTagData) { + $info['swf']['tags'][] = $TagData; } break; } diff --git a/3rdparty/getid3/module.audio.aa.php b/3rdparty/getid3/module.audio.aa.php new file mode 100644 index 0000000000..39cb77c859 --- /dev/null +++ b/3rdparty/getid3/module.audio.aa.php @@ -0,0 +1,59 @@ + // +// 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 +{ + + 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['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; + } + +} + + +?> \ No newline at end of file diff --git a/3rdparty/getid3/module.audio.aac.php b/3rdparty/getid3/module.audio.aac.php new file mode 100644 index 0000000000..d573e11d78 --- /dev/null +++ b/3rdparty/getid3/module.audio.aac.php @@ -0,0 +1,515 @@ + // +// 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 +{ + 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; + } + + + + 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; + + } + + } + + + function getAACADTSheaderFilepointer($MaxFramesToScan=1000000, $ReturnExtendedInfo=false) { + $info = &$this->getid3->info; + + // based loosely on code from AACfile by Jurgen Faul + // 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; + } + +} + + +?> \ No newline at end of file diff --git a/3rdparty/getid3/module.audio.ac3.php b/3rdparty/getid3/module.audio.ac3.php new file mode 100644 index 0000000000..ffe0174689 --- /dev/null +++ b/3rdparty/getid3/module.audio.ac3.php @@ -0,0 +1,473 @@ + // +// 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 = ''; + private $BSIoffset = 0; + + + 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 + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $this->AC3header['syncinfo'] = fread($this->getid3->fp, 5); + $thisfile_ac3_raw['synchinfo']['synchword'] = substr($this->AC3header['syncinfo'], 0, 2); + + $magic = "\x0B\x77"; + if ($thisfile_ac3_raw['synchinfo']['synchword'] != $magic) { + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($thisfile_ac3_raw['synchinfo']['synchword']).'"'; + unset($info['fileformat'], $info['ac3']); + return false; + } + + $info['audio']['dataformat'] = 'ac3'; + $info['audio']['bitrate_mode'] = 'cbr'; + $info['audio']['lossless'] = false; + + // syncinfo() { + // syncword 16 + // crc1 16 + // fscod 2 + // frmsizecod 6 + // } /* end of syncinfo */ + + $thisfile_ac3_raw['synchinfo']['crc1'] = getid3_lib::LittleEndian2Int(substr($this->AC3header['syncinfo'], 2, 2)); + $ac3_synchinfo_fscod_frmsizecod = getid3_lib::LittleEndian2Int(substr($this->AC3header['syncinfo'], 4, 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'] = $this->AC3sampleRateCodeLookup($thisfile_ac3_raw['synchinfo']['fscod']); + if ($thisfile_ac3_raw['synchinfo']['fscod'] <= 3) { + $info['audio']['sample_rate'] = $thisfile_ac3['sample_rate']; + } + + $thisfile_ac3['frame_length'] = $this->AC3frameSizeLookup($thisfile_ac3_raw['synchinfo']['frmsizecod'], $thisfile_ac3_raw['synchinfo']['fscod']); + $thisfile_ac3['bitrate'] = $this->AC3bitrateLookup($thisfile_ac3_raw['synchinfo']['frmsizecod']); + $info['audio']['bitrate'] = $thisfile_ac3['bitrate']; + + $this->AC3header['bsi'] = getid3_lib::BigEndian2Bin(fread($this->getid3->fp, 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. + $info['error'][] = 'Bit stream identification is version '.$thisfile_ac3_raw_bsi['bsid'].', but getID3() only understands up to version 8'; + unset($thisfile_ac3); + return false; + } + + $thisfile_ac3_raw_bsi['bsmod'] = $this->readHeaderBSI(3); + $thisfile_ac3_raw_bsi['acmod'] = $this->readHeaderBSI(3); + + $thisfile_ac3['service_type'] = $this->AC3serviceTypeLookup($thisfile_ac3_raw_bsi['bsmod'], $thisfile_ac3_raw_bsi['acmod']); + $ac3_coding_mode = $this->AC3audioCodingModeLookup($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'] = $this->AC3centerMixLevelLookup($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'] = $this->AC3surroundMixLevelLookup($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'] = $this->AC3dolbySurroundModeLookup($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'] = $this->AC3channelsEnabledLookup($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'] = $this->AC3heavyCompression($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'] = $this->AC3roomTypeLookup($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'] = $this->AC3heavyCompression($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'] = $this->AC3roomTypeLookup($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(fread($this->getid3->fp, $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 AC3sampleRateCodeLookup($fscod) { + static $AC3sampleRateCodeLookup = 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($AC3sampleRateCodeLookup[$fscod]) ? $AC3sampleRateCodeLookup[$fscod] : false); + } + + public static function AC3serviceTypeLookup($bsmod, $acmod) { + static $AC3serviceTypeLookup = array(); + if (empty($AC3serviceTypeLookup)) { + for ($i = 0; $i <= 7; $i++) { + $AC3serviceTypeLookup[0][$i] = 'main audio service: complete main (CM)'; + $AC3serviceTypeLookup[1][$i] = 'main audio service: music and effects (ME)'; + $AC3serviceTypeLookup[2][$i] = 'associated service: visually impaired (VI)'; + $AC3serviceTypeLookup[3][$i] = 'associated service: hearing impaired (HI)'; + $AC3serviceTypeLookup[4][$i] = 'associated service: dialogue (D)'; + $AC3serviceTypeLookup[5][$i] = 'associated service: commentary (C)'; + $AC3serviceTypeLookup[6][$i] = 'associated service: emergency (E)'; + } + + $AC3serviceTypeLookup[7][1] = 'associated service: voice over (VO)'; + for ($i = 2; $i <= 7; $i++) { + $AC3serviceTypeLookup[7][$i] = 'main audio service: karaoke'; + } + } + return (isset($AC3serviceTypeLookup[$bsmod][$acmod]) ? $AC3serviceTypeLookup[$bsmod][$acmod] : false); + } + + public static function AC3audioCodingModeLookup($acmod) { + static $AC3audioCodingModeLookup = array(); + if (empty($AC3audioCodingModeLookup)) { + // array(channel configuration, # channels (not incl LFE), channel order) + $AC3audioCodingModeLookup = 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($AC3audioCodingModeLookup[$acmod]) ? $AC3audioCodingModeLookup[$acmod] : false); + } + + public static function AC3centerMixLevelLookup($cmixlev) { + static $AC3centerMixLevelLookup; + if (empty($AC3centerMixLevelLookup)) { + $AC3centerMixLevelLookup = 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($AC3centerMixLevelLookup[$cmixlev]) ? $AC3centerMixLevelLookup[$cmixlev] : false); + } + + public static function AC3surroundMixLevelLookup($surmixlev) { + static $AC3surroundMixLevelLookup; + if (empty($AC3surroundMixLevelLookup)) { + $AC3surroundMixLevelLookup = array( + 0 => pow(2, -3.0 / 6), + 1 => pow(2, -6.0 / 6), + 2 => 0, + 3 => 'reserved' + ); + } + return (isset($AC3surroundMixLevelLookup[$surmixlev]) ? $AC3surroundMixLevelLookup[$surmixlev] : false); + } + + public static function AC3dolbySurroundModeLookup($dsurmod) { + static $AC3dolbySurroundModeLookup = array( + 0 => 'not indicated', + 1 => 'Not Dolby Surround encoded', + 2 => 'Dolby Surround encoded', + 3 => 'reserved' + ); + return (isset($AC3dolbySurroundModeLookup[$dsurmod]) ? $AC3dolbySurroundModeLookup[$dsurmod] : false); + } + + public static function AC3channelsEnabledLookup($acmod, $lfeon) { + $AC3channelsEnabledLookup = 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: + $AC3channelsEnabledLookup['surround_mono'] = true; + break; + case 6: + case 7: + $AC3channelsEnabledLookup['surround_left'] = true; + $AC3channelsEnabledLookup['surround_right'] = true; + break; + } + return $AC3channelsEnabledLookup; + } + + public static function AC3heavyCompression($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 AC3roomTypeLookup($roomtyp) { + static $AC3roomTypeLookup = array( + 0 => 'not indicated', + 1 => 'large room, X curve monitor', + 2 => 'small room, flat monitor', + 3 => 'reserved' + ); + return (isset($AC3roomTypeLookup[$roomtyp]) ? $AC3roomTypeLookup[$roomtyp] : false); + } + + public static function AC3frameSizeLookup($frmsizecod, $fscod) { + $padding = (bool) ($frmsizecod % 2); + $framesizeid = floor($frmsizecod / 2); + + static $AC3frameSizeLookup = array(); + if (empty($AC3frameSizeLookup)) { + $AC3frameSizeLookup = 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 + $AC3frameSizeLookup[$frmsizecod] += 2; + } + return (isset($AC3frameSizeLookup[$framesizeid][$fscod]) ? $AC3frameSizeLookup[$framesizeid][$fscod] : false); + } + + public static function AC3bitrateLookup($frmsizecod) { + $framesizeid = floor($frmsizecod / 2); + + static $AC3bitrateLookup = 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($AC3bitrateLookup[$framesizeid]) ? $AC3bitrateLookup[$framesizeid] : false); + } + + +} + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/module.audio.au.php b/3rdparty/getid3/module.audio.au.php similarity index 72% rename from apps/media/getID3/getid3/module.audio.au.php rename to 3rdparty/getid3/module.audio.au.php index afbc75d671..a1094dbcda 100644 --- a/apps/media/getID3/getid3/module.audio.au.php +++ b/3rdparty/getid3/module.audio.au.php @@ -14,31 +14,33 @@ ///////////////////////////////////////////////////////////////// -class getid3_au +class getid3_au extends getid3_handler { - function getid3_au(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $AUheader = fread($fd, 8); + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $AUheader = fread($this->getid3->fp, 8); - if (substr($AUheader, 0, 4) != '.snd') { - $ThisFileInfo['error'][] = 'Expecting ".snd" at offset '.$ThisFileInfo['avdataoffset'].', found "'.substr($AUheader, 0, 4).'"'; + $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 - $ThisFileInfo['au'] = array(); - $thisfile_au = &$ThisFileInfo['au']; + $info['au'] = array(); + $thisfile_au = &$info['au']; - $ThisFileInfo['fileformat'] = 'au'; - $ThisFileInfo['audio']['dataformat'] = 'au'; - $ThisFileInfo['audio']['bitrate_mode'] = 'cbr'; + $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($fd, $thisfile_au['header_length'] - 8); - $ThisFileInfo['avdataoffset'] += $thisfile_au['header_length']; + $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)); @@ -49,20 +51,20 @@ class getid3_au $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'])) { - $ThisFileInfo['audio']['bits_per_sample'] = $thisfile_au['bits_per_sample']; + $info['audio']['bits_per_sample'] = $thisfile_au['bits_per_sample']; } else { unset($thisfile_au['bits_per_sample']); } - $ThisFileInfo['audio']['sample_rate'] = $thisfile_au['sample_rate']; - $ThisFileInfo['audio']['channels'] = $thisfile_au['channels']; + $info['audio']['sample_rate'] = $thisfile_au['sample_rate']; + $info['audio']['channels'] = $thisfile_au['channels']; - if (($ThisFileInfo['avdataoffset'] + $thisfile_au['data_size']) > $ThisFileInfo['avdataend']) { - $ThisFileInfo['warning'][] = 'Possible truncated file - expecting "'.$thisfile_au['data_size'].'" bytes of audio data, only found '.($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']).' bytes"'; + 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"'; } - $ThisFileInfo['playtime_seconds'] = $thisfile_au['data_size'] / ($thisfile_au['sample_rate'] * $thisfile_au['channels'] * ($thisfile_au['used_bits_per_sample'] / 8)); - $ThisFileInfo['audio']['bitrate'] = ($thisfile_au['data_size'] * 8) / $ThisFileInfo['playtime_seconds']; + $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; } diff --git a/3rdparty/getid3/module.audio.avr.php b/3rdparty/getid3/module.audio.avr.php new file mode 100644 index 0000000000..9c6d665078 --- /dev/null +++ b/3rdparty/getid3/module.audio.avr.php @@ -0,0 +1,127 @@ + // +// 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 +{ + + 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; + } + +} + + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/module.audio.bonk.php b/3rdparty/getid3/module.audio.bonk.php similarity index 53% rename from apps/media/getID3/getid3/module.audio.bonk.php rename to 3rdparty/getid3/module.audio.bonk.php index fef9782cfc..9f5187e3c0 100644 --- a/apps/media/getID3/getid3/module.audio.bonk.php +++ b/3rdparty/getid3/module.audio.bonk.php @@ -14,67 +14,68 @@ ///////////////////////////////////////////////////////////////// -class getid3_bonk +class getid3_bonk extends getid3_handler { - function getid3_bonk(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; // shortcut - $ThisFileInfo['bonk'] = array(); - $thisfile_bonk = &$ThisFileInfo['bonk']; + $info['bonk'] = array(); + $thisfile_bonk = &$info['bonk']; - $thisfile_bonk['dataoffset'] = $ThisFileInfo['avdataoffset']; - $thisfile_bonk['dataend'] = $ThisFileInfo['avdataend']; + $thisfile_bonk['dataoffset'] = $info['avdataoffset']; + $thisfile_bonk['dataend'] = $info['avdataend']; - if ($thisfile_bonk['dataend'] >= pow(2, 31)) { + if (!getid3_lib::intValueSupported($thisfile_bonk['dataend'])) { - $ThisFileInfo['warning'][] = 'Unable to parse BONK file from end (v0.6+ preferred method) because PHP filesystem functions only support up to 2GB'; + $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($fd, $thisfile_bonk['dataend'] - 8, SEEK_SET); - $PossibleBonkTag = fread($fd, 8); + 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($fd, 0 - $BonkTagSize, SEEK_CUR); - $BonkTagOffset = ftell($fd); - $TagHeaderTest = fread($fd, 5); + 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)))) { - $ThisFileInfo['error'][] = 'Expecting "Ø'.strtoupper(substr($PossibleBonkTag, 4, 4)).'" at offset '.$BonkTagOffset.', found "'.$TagHeaderTest.'"'; + $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($fd, $BonkTagName, $ThisFileInfo); + $this->HandleBonkTags($BonkTagName); $NextTagEndOffset = $BonkTagOffset - 8; if ($NextTagEndOffset < $thisfile_bonk['dataoffset']) { - if (empty($ThisFileInfo['audio']['encoder'])) { - $ThisFileInfo['audio']['encoder'] = 'Extended BONK v0.9+'; + if (empty($info['audio']['encoder'])) { + $info['audio']['encoder'] = 'Extended BONK v0.9+'; } return true; } - fseek($fd, $NextTagEndOffset, SEEK_SET); - $PossibleBonkTag = fread($fd, 8); + 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($fd, $thisfile_bonk['dataoffset'], SEEK_SET); + fseek($this->getid3->fp, $thisfile_bonk['dataoffset'], SEEK_SET); do { - $TagHeaderTest = fread($fd, 5); + $TagHeaderTest = fread($this->getid3->fp, 5); switch ($TagHeaderTest) { case "\x00".'BONK': - if (empty($ThisFileInfo['audio']['encoder'])) { - $ThisFileInfo['audio']['encoder'] = 'BONK v0.4'; + if (empty($info['audio']['encoder'])) { + $info['audio']['encoder'] = 'BONK v0.4'; } break; case "\x00".'INFO': - $ThisFileInfo['audio']['encoder'] = 'Extended BONK v0.5'; + $info['audio']['encoder'] = 'Extended BONK v0.5'; break; default: @@ -83,43 +84,43 @@ class getid3_bonk $BonkTagName = substr($TagHeaderTest, 1, 4); $thisfile_bonk[$BonkTagName]['size'] = $thisfile_bonk['dataend'] - $thisfile_bonk['dataoffset']; $thisfile_bonk[$BonkTagName]['offset'] = $thisfile_bonk['dataoffset']; - $this->HandleBonkTags($fd, $BonkTagName, $ThisFileInfo); + $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($fd, $thisfile_bonk['META']['tags']['info'], SEEK_SET); - $TagHeaderTest = fread($fd, 5); + fseek($this->getid3->fp, $thisfile_bonk['META']['tags']['info'], SEEK_SET); + $TagHeaderTest = fread($this->getid3->fp, 5); if ($TagHeaderTest == "\x00".'INFO') { - $ThisFileInfo['audio']['encoder'] = 'Extended BONK v0.6 - v0.8'; + $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($fd, $BonkTagName, $ThisFileInfo); + $this->HandleBonkTags($BonkTagName); } } - if (empty($ThisFileInfo['audio']['encoder'])) { - $ThisFileInfo['audio']['encoder'] = 'Extended BONK v0.9+'; + if (empty($info['audio']['encoder'])) { + $info['audio']['encoder'] = 'Extended BONK v0.9+'; } if (empty($thisfile_bonk['BONK'])) { - unset($ThisFileInfo['bonk']); + unset($info['bonk']); } return true; } - function HandleBonkTags(&$fd, &$BonkTagName, &$ThisFileInfo) { - + function HandleBonkTags($BonkTagName) { + $info = &$this->getid3->info; switch ($BonkTagName) { case 'BONK': // shortcut - $thisfile_bonk_BONK = &$ThisFileInfo['bonk']['BONK']; + $thisfile_bonk_BONK = &$info['bonk']['BONK']; - $BonkData = "\x00".'BONK'.fread($fd, 17); + $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)); @@ -131,40 +132,40 @@ class getid3_bonk $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)); - $ThisFileInfo['avdataoffset'] = $thisfile_bonk_BONK['offset'] + 5 + 17; - $ThisFileInfo['avdataend'] = $thisfile_bonk_BONK['offset'] + $thisfile_bonk_BONK['size']; + $info['avdataoffset'] = $thisfile_bonk_BONK['offset'] + 5 + 17; + $info['avdataend'] = $thisfile_bonk_BONK['offset'] + $thisfile_bonk_BONK['size']; - $ThisFileInfo['fileformat'] = 'bonk'; - $ThisFileInfo['audio']['dataformat'] = 'bonk'; - $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; // assumed - $ThisFileInfo['audio']['channels'] = $thisfile_bonk_BONK['channels']; - $ThisFileInfo['audio']['sample_rate'] = $thisfile_bonk_BONK['sample_rate']; - $ThisFileInfo['audio']['channelmode'] = ($thisfile_bonk_BONK['joint_stereo'] ? 'joint stereo' : 'stereo'); - $ThisFileInfo['audio']['lossless'] = $thisfile_bonk_BONK['lossless']; - $ThisFileInfo['audio']['codec'] = 'bonk'; + $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'; - $ThisFileInfo['playtime_seconds'] = $thisfile_bonk_BONK['number_samples'] / ($thisfile_bonk_BONK['sample_rate'] * $thisfile_bonk_BONK['channels']); - if ($ThisFileInfo['playtime_seconds'] > 0) { - $ThisFileInfo['audio']['bitrate'] = (($ThisFileInfo['bonk']['dataend'] - $ThisFileInfo['bonk']['dataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; + $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 = &$ThisFileInfo['bonk']['INFO']; + $thisfile_bonk_INFO = &$info['bonk']['INFO']; - $thisfile_bonk_INFO['version'] = getid3_lib::LittleEndian2Int(fread($fd, 1)); + $thisfile_bonk_INFO['version'] = getid3_lib::LittleEndian2Int(fread($this->getid3->fp, 1)); $thisfile_bonk_INFO['entries_count'] = 0; - $NextInfoDataPair = fread($fd, 5); + $NextInfoDataPair = fread($this->getid3->fp, 5); if (!$this->BonkIsValidTagName(substr($NextInfoDataPair, 1, 4))) { - while (!feof($fd)) { + 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($fd, 5); + $NextInfoDataPair = fread($this->getid3->fp, 5); if ($this->BonkIsValidTagName(substr($NextInfoDataPair, 1, 4))) { - fseek($fd, -5, SEEK_CUR); + fseek($this->getid3->fp, -5, SEEK_CUR); break; } $thisfile_bonk_INFO['entries_count']++; @@ -173,37 +174,45 @@ class getid3_bonk break; case 'META': - $BonkData = "\x00".'META'.fread($fd, $ThisFileInfo['bonk']['META']['size'] - 5); - $ThisFileInfo['bonk']['META']['version'] = getid3_lib::LittleEndian2Int(substr($BonkData, 5, 1)); + $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); + $MetaEntryTagName = substr($BonkData, $offset, 4); $offset += 4; $MetaEntryTagOffset = getid3_lib::LittleEndian2Int(substr($BonkData, $offset, 4)); $offset += 4; - $ThisFileInfo['bonk']['META']['tags'][$MetaEntryTagName] = $MetaEntryTagOffset; + $info['bonk']['META']['tags'][$MetaEntryTagName] = $MetaEntryTagOffset; } break; case ' ID3': - $ThisFileInfo['audio']['encoder'] = 'Extended BONK v0.9+'; + $info['audio']['encoder'] = 'Extended BONK v0.9+'; // ID3v2 checking is optional if (class_exists('getid3_id3v2')) { - $ThisFileInfo['bonk'][' ID3']['valid'] = new getid3_id3v2($fd, $ThisFileInfo, $ThisFileInfo['bonk'][' ID3']['offset'] + 2); + $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: - $ThisFileInfo['warning'][] = 'Unexpected Bonk tag "'.$BonkTagName.'" at offset '.$ThisFileInfo['bonk'][$BonkTagName]['offset']; + $info['warning'][] = 'Unexpected Bonk tag "'.$BonkTagName.'" at offset '.$info['bonk'][$BonkTagName]['offset']; break; } } - function BonkIsValidTagName($PossibleBonkTag, $ignorecase=false) { + static function BonkIsValidTagName($PossibleBonkTag, $ignorecase=false) { static $BonkIsValidTagName = array('BONK', 'INFO', ' ID3', 'META'); foreach ($BonkIsValidTagName as $validtagname) { if ($validtagname == $PossibleBonkTag) { diff --git a/apps/media/getID3/getid3/module.audio.dss.php b/3rdparty/getid3/module.audio.dss.php similarity index 57% rename from apps/media/getID3/getid3/module.audio.dss.php rename to 3rdparty/getid3/module.audio.dss.php index b0887395c4..b7b4367629 100644 --- a/apps/media/getID3/getid3/module.audio.dss.php +++ b/3rdparty/getid3/module.audio.dss.php @@ -7,50 +7,53 @@ // See readme.txt for more details // ///////////////////////////////////////////////////////////////// // // -// module.audio.au.php // +// module.audio.dss.php // // module for analyzing Digital Speech Standard (DSS) files // // dependencies: NONE // // /// ///////////////////////////////////////////////////////////////// -class getid3_dss +class getid3_dss extends getid3_handler { - function getid3_dss(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $DSSheader = fread($fd, 1256); + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $DSSheader = fread($this->getid3->fp, 1256); - if (substr($DSSheader, 0, 4) != "\x02".'dss') { - $ThisFileInfo['error'][] = 'Expecting "[x02]dss" at offset '.$ThisFileInfo['avdataoffset'].', found "'.substr($DSSheader, 0, 4).'"'; + 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 - $ThisFileInfo['dss'] = array(); - $thisfile_dss = &$ThisFileInfo['dss']; + $info['dss'] = array(); + $thisfile_dss = &$info['dss']; - $ThisFileInfo['fileformat'] = 'dss'; - $ThisFileInfo['audio']['dataformat'] = 'dss'; - $ThisFileInfo['audio']['bitrate_mode'] = 'cbr'; + $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)); + //$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)); - //$ThisFileInfo['audio']['bits_per_sample'] = ?; - //$ThisFileInfo['audio']['sample_rate'] = ?; - $ThisFileInfo['audio']['channels'] = 1; + //$info['audio']['bits_per_sample'] = ?; + //$info['audio']['sample_rate'] = ?; + $info['audio']['channels'] = 1; - $ThisFileInfo['playtime_seconds'] = $thisfile_dss['length']; - $ThisFileInfo['audio']['bitrate'] = ($ThisFileInfo['filesize'] * 8) / $ThisFileInfo['playtime_seconds']; + $info['playtime_seconds'] = $thisfile_dss['length']; + $info['audio']['bitrate'] = ($info['filesize'] * 8) / $info['playtime_seconds']; return true; } diff --git a/3rdparty/getid3/module.audio.dts.php b/3rdparty/getid3/module.audio.dts.php new file mode 100644 index 0000000000..8102ba8bf5 --- /dev/null +++ b/3rdparty/getid3/module.audio.dts.php @@ -0,0 +1,246 @@ + // +// 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 +{ + + 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 http://mac.sourceforge.net/atl/ + + $info['fileformat'] = 'dts'; + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $DTSheader = fread($this->getid3->fp, 16); + $info['dts']['raw']['magic'] = substr($DTSheader, 0, 4); + + $magic = "\x7F\xFE\x80\x01"; + if ($info['dts']['raw']['magic'] != $magic) { + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['dts']['raw']['magic']).'"'; + unset($info['fileformat'], $info['dts']); + return false; + } + + $fhBS = getid3_lib::BigEndian2Bin(substr($DTSheader, 4, 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::DTSbitrateLookup($info['dts']['raw']['bitrate']); + $info['dts']['bits_per_sample'] = self::DTSbitPerSampleLookup($info['dts']['raw']['bits_per_sample']); + $info['dts']['sample_rate'] = self::DTSsampleRateLookup($info['dts']['raw']['sample_frequency']); + $info['dts']['dialog_normalization'] = self::DTSdialogNormalization($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::DTSnumChannelsLookup($info['dts']['raw']['channel_arrangement']); + $info['dts']['channel_arrangement'] = self::DTSchannelArrangementLookup($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); + } + + private static function DTSbitrateLookup($index) { + $DTSbitrateLookup = 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($DTSbitrateLookup[$index]) ? $DTSbitrateLookup[$index] : false); + } + + private static function DTSsampleRateLookup($index) { + $DTSsampleRateLookup = 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($DTSsampleRateLookup[$index]) ? $DTSsampleRateLookup[$index] : false); + } + + private static function DTSbitPerSampleLookup($index) { + $DTSbitPerSampleLookup = array( + 0 => 16, + 1 => 20, + 2 => 24, + 3 => 24, + ); + return (isset($DTSbitPerSampleLookup[$index]) ? $DTSbitPerSampleLookup[$index] : false); + } + + private static function DTSnumChannelsLookup($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; + } + + private static function DTSchannelArrangementLookup($index) { + $DTSchannelArrangementLookup = 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($DTSchannelArrangementLookup[$index]) ? $DTSchannelArrangementLookup[$index] : 'user-defined'); + } + + private static function DTSdialogNormalization($index, $version) { + switch ($version) { + case 7: + return 0 - $index; + break; + case 6: + return 0 - 16 - $index; + break; + } + return false; + } + +} + +?> \ No newline at end of file diff --git a/3rdparty/getid3/module.audio.flac.php b/3rdparty/getid3/module.audio.flac.php new file mode 100644 index 0000000000..98daec0fd9 --- /dev/null +++ b/3rdparty/getid3/module.audio.flac.php @@ -0,0 +1,480 @@ + // +// 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 +{ + var $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 + + function Analyze() { + $info = &$this->getid3->info; + + // http://flac.sourceforge.net/format.html + + $this->fseek($info['avdataoffset'], SEEK_SET); + $StreamMarker = $this->fread(4); + $magic = 'fLaC'; + if ($StreamMarker != $magic) { + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($StreamMarker).'"'; + return false; + } + $info['fileformat'] = 'flac'; + $info['audio']['dataformat'] = 'flac'; + $info['audio']['bitrate_mode'] = 'vbr'; + $info['audio']['lossless'] = true; + + return $this->FLACparseMETAdata(); + } + + + function FLACparseMETAdata() { + $info = &$this->getid3->info; + do { + $METAdataBlockOffset = $this->ftell(); + $METAdataBlockHeader = $this->fread(4); + $METAdataLastBlockFlag = (bool) (getid3_lib::BigEndian2Int(substr($METAdataBlockHeader, 0, 1)) & 0x80); + $METAdataBlockType = getid3_lib::BigEndian2Int(substr($METAdataBlockHeader, 0, 1)) & 0x7F; + $METAdataBlockLength = getid3_lib::BigEndian2Int(substr($METAdataBlockHeader, 1, 3)); + $METAdataBlockTypeText = getid3_flac::FLACmetaBlockTypeLookup($METAdataBlockType); + + if ($METAdataBlockLength < 0) { + $info['error'][] = 'corrupt or invalid METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$METAdataBlockType.') at offset '.$METAdataBlockOffset; + break; + } + + $info['flac'][$METAdataBlockTypeText]['raw'] = array(); + $ThisFileInfo_flac_METAdataBlockTypeText_raw = &$info['flac'][$METAdataBlockTypeText]['raw']; + + $ThisFileInfo_flac_METAdataBlockTypeText_raw['offset'] = $METAdataBlockOffset; + $ThisFileInfo_flac_METAdataBlockTypeText_raw['last_meta_block'] = $METAdataLastBlockFlag; + $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_type'] = $METAdataBlockType; + $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_type_text'] = $METAdataBlockTypeText; + $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_length'] = $METAdataBlockLength; + if (($METAdataBlockOffset + 4 + $METAdataBlockLength) > $info['avdataend']) { + $info['error'][] = 'METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$METAdataBlockType.') at offset '.$METAdataBlockOffset.' extends beyond end of file'; + break; + } + if ($METAdataBlockLength < 1) { + $info['error'][] = 'METADATA_BLOCK_HEADER.BLOCK_LENGTH ('.$METAdataBlockLength.') at offset '.$METAdataBlockOffset.' is invalid'; + break; + } + $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'] = $this->fread($METAdataBlockLength); + $info['avdataoffset'] = $this->ftell(); + + switch ($METAdataBlockTypeText) { + case 'STREAMINFO': // 0x00 + if (!$this->FLACparseSTREAMINFO($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) { + return false; + } + break; + + case 'PADDING': // 0x01 + // ignore + break; + + case 'APPLICATION': // 0x02 + if (!$this->FLACparseAPPLICATION($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) { + return false; + } + break; + + case 'SEEKTABLE': // 0x03 + if (!$this->FLACparseSEEKTABLE($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) { + return false; + } + break; + + case 'VORBIS_COMMENT': // 0x04 + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = $this->ftell() - $METAdataBlockLength; + $getid3_temp->info['audio']['dataformat'] = 'flac'; + $getid3_temp->info['flac'] = $info['flac']; + $getid3_ogg = new getid3_ogg($getid3_temp); + $getid3_ogg->ParseVorbisCommentsFilepointer(); + $maybe_copy_keys = array('vendor', 'comments_raw', 'comments', 'replay_gain'); + foreach ($maybe_copy_keys as $maybe_copy_key) { + if (!empty($getid3_temp->info['ogg'][$maybe_copy_key])) { + $info['ogg'][$maybe_copy_key] = $getid3_temp->info['ogg'][$maybe_copy_key]; + } + } + if (!empty($getid3_temp->info['replay_gain'])) { + $info['replay_gain'] = $getid3_temp->info['replay_gain']; + } + unset($getid3_temp, $getid3_ogg); + break; + + case 'CUESHEET': // 0x05 + if (!getid3_flac::FLACparseCUESHEET($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) { + return false; + } + break; + + case 'PICTURE': // 0x06 + if (!getid3_flac::FLACparsePICTURE($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) { + return false; + } + break; + + default: + $info['warning'][] = 'Unhandled METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$METAdataBlockType.') at offset '.$METAdataBlockOffset; + break; + } + + } while ($METAdataLastBlockFlag === false); + + if (isset($info['flac']['PICTURE'])) { + foreach ($info['flac']['PICTURE'] as $key => $valuearray) { + if (!empty($valuearray['image_mime']) && !empty($valuearray['data'])) { + $info['ogg']['comments']['picture'][] = array('image_mime'=>$valuearray['image_mime'], 'data'=>$valuearray['data']); + } + } + } + + if (isset($info['flac']['STREAMINFO'])) { + $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) { + $info['error'][] = 'Corrupt FLAC file: uncompressed_audio_bytes == zero'; + return false; + } + $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)) { + + $info['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']); + } + + } + + } + + $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 + $info['warning'][] = 'FLAC calculates MD5 data strangely on 8-bit audio, so the stored md5_data_source value will not match the decoded WAV file'; + } + if (!empty($info['ogg']['vendor'])) { + $info['audio']['encoder'] = $info['ogg']['vendor']; + } + + return true; + } + + static function FLACmetaBlockTypeLookup($blocktype) { + static $FLACmetaBlockTypeLookup = array(); + if (empty($FLACmetaBlockTypeLookup)) { + $FLACmetaBlockTypeLookup[0] = 'STREAMINFO'; + $FLACmetaBlockTypeLookup[1] = 'PADDING'; + $FLACmetaBlockTypeLookup[2] = 'APPLICATION'; + $FLACmetaBlockTypeLookup[3] = 'SEEKTABLE'; + $FLACmetaBlockTypeLookup[4] = 'VORBIS_COMMENT'; + $FLACmetaBlockTypeLookup[5] = 'CUESHEET'; + $FLACmetaBlockTypeLookup[6] = 'PICTURE'; + } + return (isset($FLACmetaBlockTypeLookup[$blocktype]) ? $FLACmetaBlockTypeLookup[$blocktype] : 'reserved'); + } + + static function FLACapplicationIDLookup($applicationid) { + static $FLACapplicationIDLookup = array(); + if (empty($FLACapplicationIDLookup)) { + // http://flac.sourceforge.net/id.html + $FLACapplicationIDLookup[0x46746F6C] = 'flac-tools'; // 'Ftol' + $FLACapplicationIDLookup[0x46746F6C] = 'Sound Font FLAC'; // 'SFFL' + } + return (isset($FLACapplicationIDLookup[$applicationid]) ? $FLACapplicationIDLookup[$applicationid] : 'reserved'); + } + + static function FLACpictureTypeLookup($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'); + } + + function FLACparseSTREAMINFO($METAdataBlockData) { + $info = &$this->getid3->info; + + $offset = 0; + $info['flac']['STREAMINFO']['min_block_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 2)); + $offset += 2; + $info['flac']['STREAMINFO']['max_block_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 2)); + $offset += 2; + $info['flac']['STREAMINFO']['min_frame_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 3)); + $offset += 3; + $info['flac']['STREAMINFO']['max_frame_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 3)); + $offset += 3; + + $SampleRateChannelsSampleBitsStreamSamples = getid3_lib::BigEndian2Bin(substr($METAdataBlockData, $offset, 8)); + $info['flac']['STREAMINFO']['sample_rate'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 0, 20)); + $info['flac']['STREAMINFO']['channels'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 20, 3)) + 1; + $info['flac']['STREAMINFO']['bits_per_sample'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 23, 5)) + 1; + $info['flac']['STREAMINFO']['samples_stream'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 28, 36)); + $offset += 8; + + $info['flac']['STREAMINFO']['audio_signature'] = substr($METAdataBlockData, $offset, 16); + $offset += 16; + + if (!empty($info['flac']['STREAMINFO']['sample_rate'])) { + + $info['audio']['bitrate_mode'] = 'vbr'; + $info['audio']['sample_rate'] = $info['flac']['STREAMINFO']['sample_rate']; + $info['audio']['channels'] = $info['flac']['STREAMINFO']['channels']; + $info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample']; + $info['playtime_seconds'] = $info['flac']['STREAMINFO']['samples_stream'] / $info['flac']['STREAMINFO']['sample_rate']; + if ($info['playtime_seconds'] > 0) { + $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; + } + + } else { + $info['error'][] = 'Corrupt METAdata block: STREAMINFO'; + return false; + } + unset($info['flac']['STREAMINFO']['raw']); + return true; + } + + + function FLACparseAPPLICATION($METAdataBlockData) { + $info = &$this->getid3->info; + + $offset = 0; + $ApplicationID = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 4)); + $offset += 4; + $info['flac']['APPLICATION'][$ApplicationID]['name'] = getid3_flac::FLACapplicationIDLookup($ApplicationID); + $info['flac']['APPLICATION'][$ApplicationID]['data'] = substr($METAdataBlockData, $offset); + $offset = $METAdataBlockLength; + + unset($info['flac']['APPLICATION']['raw']); + return true; + } + + + function FLACparseSEEKTABLE($METAdataBlockData) { + $info = &$this->getid3->info; + + $offset = 0; + $METAdataBlockLength = strlen($METAdataBlockData); + $placeholderpattern = str_repeat("\xFF", 8); + while ($offset < $METAdataBlockLength) { + $SampleNumberString = substr($METAdataBlockData, $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($METAdataBlockData, $offset, 8)); + $offset += 8; + $info['flac']['SEEKTABLE'][$SampleNumber]['samples'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 2)); + $offset += 2; + + } + } + + unset($info['flac']['SEEKTABLE']['raw']); + + return true; + } + + function FLACparseCUESHEET($METAdataBlockData) { + $info = &$this->getid3->info; + $offset = 0; + $info['flac']['CUESHEET']['media_catalog_number'] = trim(substr($METAdataBlockData, $offset, 128), "\0"); + $offset += 128; + $info['flac']['CUESHEET']['lead_in_samples'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 8)); + $offset += 8; + $info['flac']['CUESHEET']['flags']['is_cd'] = (bool) (getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)) & 0x80); + $offset += 1; + + $offset += 258; // reserved + + $info['flac']['CUESHEET']['number_tracks'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)); + $offset += 1; + + for ($track = 0; $track < $info['flac']['CUESHEET']['number_tracks']; $track++) { + $TrackSampleOffset = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 8)); + $offset += 8; + $TrackNumber = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)); + $offset += 1; + + $info['flac']['CUESHEET']['tracks'][$TrackNumber]['sample_offset'] = $TrackSampleOffset; + + $info['flac']['CUESHEET']['tracks'][$TrackNumber]['isrc'] = substr($METAdataBlockData, $offset, 12); + $offset += 12; + + $TrackFlagsRaw = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $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($METAdataBlockData, $offset, 1)); + $offset += 1; + + for ($index = 0; $index < $info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points']; $index++) { + $IndexSampleOffset = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 8)); + $offset += 8; + $IndexNumber = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)); + $offset += 1; + + $offset += 3; // reserved + + $info['flac']['CUESHEET']['tracks'][$TrackNumber]['indexes'][$IndexNumber] = $IndexSampleOffset; + } + } + + unset($info['flac']['CUESHEET']['raw']); + + return true; + } + + + function FLACparsePICTURE($meta_data_block_data) { + $info = &$this->getid3->info; + $picture = &$info['flac']['PICTURE'][sizeof($info['flac']['PICTURE']) - 1]; + $picture['offset'] = $info['flac']['PICTURE']['raw']['offset']; + unset($info['flac']['PICTURE']['raw']); + + $offset = 0; + + $picture['typeid'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); + $picture['type'] = getid3_flac::FLACpictureTypeLookup($picture['typeid']); + $offset += 4; + + $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); + $offset += 4; + + $picture['image_mime'] = substr($meta_data_block_data, $offset, $length); + $offset += $length; + + $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); + $offset += 4; + + $picture['description'] = substr($meta_data_block_data, $offset, $length); + $offset += $length; + + $picture['width'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); + $offset += 4; + + $picture['height'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); + $offset += 4; + + $picture['color_depth'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); + $offset += 4; + + $picture['colors_indexed'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); + $offset += 4; + + $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); + $offset += 4; + + $picture['data'] = substr($meta_data_block_data, $offset, $length); + $offset += $length; + $picture['data_length'] = strlen($picture['data']); + + + do { + if ($this->inline_attachments === false) { + // skip entirely + unset($picture['data']); + break; + } + if ($this->inline_attachments === true) { + // great + } elseif (is_int($this->inline_attachments)) { + if ($this->inline_attachments < $picture['data_length']) { + // too big, skip + $info['warning'][] = 'attachment at '.$picture['offset'].' is too large to process inline ('.number_format($picture['data_length']).' bytes)'; + unset($picture['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 '.$picture['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)'; + unset($picture['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']).'_'.$picture['offset']; + if (!file_exists($destination_filename) || is_writable($destination_filename)) { + file_put_contents($destination_filename, $picture['data']); + } else { + $info['warning'][] = 'attachment at '.$picture['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)'; + } + $picture['data_filename'] = $destination_filename; + unset($picture['data']); + } else { + if (!isset($info['flac']['comments']['picture'])) { + $info['flac']['comments']['picture'] = array(); + } + $info['flac']['comments']['picture'][] = array('data'=>$picture['data'], 'image_mime'=>$picture['image_mime']); + } + } while (false); + + + + return true; + } +} + +?> \ No newline at end of file diff --git a/3rdparty/getid3/module.audio.la.php b/3rdparty/getid3/module.audio.la.php new file mode 100644 index 0000000000..98d80a6dc6 --- /dev/null +++ b/3rdparty/getid3/module.audio.la.php @@ -0,0 +1,229 @@ + // +// 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 +{ + + 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']['codec'] = RIFFwFormatTagLookup($info['la']['raw']['format']); + $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; + } + +} + + +?> \ No newline at end of file diff --git a/3rdparty/getid3/module.audio.lpac.php b/3rdparty/getid3/module.audio.lpac.php new file mode 100644 index 0000000000..6ef0fb8acd --- /dev/null +++ b/3rdparty/getid3/module.audio.lpac.php @@ -0,0 +1,130 @@ + // +// 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 +{ + + 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; + } + +} + + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/module.audio.midi.php b/3rdparty/getid3/module.audio.midi.php similarity index 87% rename from apps/media/getID3/getid3/module.audio.midi.php rename to 3rdparty/getid3/module.audio.midi.php index 0fd7c9bdbf..7b839cf140 100644 --- a/apps/media/getID3/getid3/module.audio.midi.php +++ b/3rdparty/getid3/module.audio.midi.php @@ -13,27 +13,31 @@ // /// ///////////////////////////////////////////////////////////////// +define('GETID3_MIDI_MAGIC_MTHD', 'MThd'); // MIDI file header magic +define('GETID3_MIDI_MAGIC_MTRK', 'MTrk'); // MIDI track header magic -class getid3_midi +class getid3_midi extends getid3_handler { + var $scanwholefile = true; - function getid3_midi(&$fd, &$ThisFileInfo, $scanwholefile=true) { + function Analyze() { + $info = &$this->getid3->info; // shortcut - $ThisFileInfo['midi']['raw'] = array(); - $thisfile_midi = &$ThisFileInfo['midi']; + $info['midi']['raw'] = array(); + $thisfile_midi = &$info['midi']; $thisfile_midi_raw = &$thisfile_midi['raw']; - $ThisFileInfo['fileformat'] = 'midi'; - $ThisFileInfo['audio']['dataformat'] = 'midi'; + $info['fileformat'] = 'midi'; + $info['audio']['dataformat'] = 'midi'; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $MIDIdata = fread($fd, GETID3_FREAD_BUFFER_SIZE); + 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 != 'MThd') { - $ThisFileInfo['error'][] = 'Expecting "MThd" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$MIDIheaderID.'"'; - unset($ThisFileInfo['fileformat']); + 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; @@ -47,33 +51,33 @@ class getid3_midi $offset += 2; for ($i = 0; $i < $thisfile_midi_raw['tracks']; $i++) { - if ((strlen($MIDIdata) - $offset) < 8) { - $MIDIdata .= fread($fd, GETID3_FREAD_BUFFER_SIZE); + while ((strlen($MIDIdata) - $offset) < 8) { + $MIDIdata .= fread($this->getid3->fp, $this->getid3->fread_buffer_size()); } $trackID = substr($MIDIdata, $offset, 4); $offset += 4; - if ($trackID == 'MTrk') { + 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 { - $ThisFileInfo['error'][] = 'Expecting "MTrk" at '.$offset.', found '.$trackID.' instead'; + $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)) { - $ThisFileInfo['error'][] = 'Cannot find MIDI track information'; + $info['error'][] = 'Cannot find MIDI track information'; unset($thisfile_midi); - unset($ThisFileInfo['fileformat']); + unset($info['fileformat']); return false; } - if ($scanwholefile) { // this can take quite a long time, so have the option to bypass it if speed is very important + 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; - $ThisFileInfo['playtime_seconds'] = 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 (); @@ -215,7 +219,7 @@ class getid3_midi case 0x51: // Tempo: microseconds / quarter note $CurrentMicroSecondsPerBeat = getid3_lib::BigEndian2Int(substr($METAeventData, 0, $METAeventLength)); if ($CurrentMicroSecondsPerBeat == 0) { - $ThisFileInfo['error'][] = 'Corrupt MIDI file: CurrentMicroSecondsPerBeat == zero'; + $info['error'][] = 'Corrupt MIDI file: CurrentMicroSecondsPerBeat == zero'; return false; } $thisfile_midi_raw['events'][$tracknumber][$CumulativeDeltaTime]['us_qnote'] = $CurrentMicroSecondsPerBeat; @@ -258,13 +262,13 @@ class getid3_midi break; default: - $ThisFileInfo['warning'][] = 'Unhandled META Event Command: '.$METAeventCommand; + $info['warning'][] = 'Unhandled META Event Command: '.$METAeventCommand; break; } } else { - $ThisFileInfo['warning'][] = 'Unhandled MIDI Event ID: '.$MIDIevents[$tracknumber][$eventid]['eventid'].' + Channel ID: '.$MIDIevents[$tracknumber][$eventid]['channel']; + $info['warning'][] = 'Unhandled MIDI Event ID: '.$MIDIevents[$tracknumber][$eventid]['eventid'].' + Channel ID: '.$MIDIevents[$tracknumber][$eventid]['channel']; } } @@ -284,11 +288,11 @@ class getid3_midi if ($thisfile_midi['totalticks'] > $tickoffset) { if ($thisfile_midi_raw['ticksperqnote'] == 0) { - $ThisFileInfo['error'][] = 'Corrupt MIDI file: ticksperqnote == zero'; + $info['error'][] = 'Corrupt MIDI file: ticksperqnote == zero'; return false; } - $ThisFileInfo['playtime_seconds'] += (($tickoffset - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote']) * ($prevmicrosecondsperbeat / 1000000); + $info['playtime_seconds'] += (($tickoffset - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote']) * ($prevmicrosecondsperbeat / 1000000); $prevmicrosecondsperbeat = $microsecondsperbeat; $previoustickoffset = $tickoffset; @@ -297,18 +301,18 @@ class getid3_midi if ($thisfile_midi['totalticks'] > $previoustickoffset) { if ($thisfile_midi_raw['ticksperqnote'] == 0) { - $ThisFileInfo['error'][] = 'Corrupt MIDI file: ticksperqnote == zero'; + $info['error'][] = 'Corrupt MIDI file: ticksperqnote == zero'; return false; } - $ThisFileInfo['playtime_seconds'] += (($thisfile_midi['totalticks'] - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote']) * ($microsecondsperbeat / 1000000); + $info['playtime_seconds'] += (($thisfile_midi['totalticks'] - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote']) * ($microsecondsperbeat / 1000000); } } - - if (@$ThisFileInfo['playtime_seconds'] > 0) { - $ThisFileInfo['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; + + if (!empty($info['playtime_seconds'])) { + $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; } if (!empty($thisfile_midi['lyrics'])) { diff --git a/3rdparty/getid3/module.audio.mod.php b/3rdparty/getid3/module.audio.mod.php new file mode 100644 index 0000000000..b881769426 --- /dev/null +++ b/3rdparty/getid3/module.audio.mod.php @@ -0,0 +1,101 @@ + // +// 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 +{ + + 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; + } + + + 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; + } + + 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; + } + + 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; + } + + 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; + } + +} + + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/module.audio.monkey.php b/3rdparty/getid3/module.audio.monkey.php similarity index 78% rename from apps/media/getID3/getid3/module.audio.monkey.php rename to 3rdparty/getid3/module.audio.monkey.php index 42382ad15e..ffaeae9f07 100644 --- a/apps/media/getID3/getid3/module.audio.monkey.php +++ b/3rdparty/getid3/module.audio.monkey.php @@ -14,29 +14,32 @@ ///////////////////////////////////////////////////////////////// -class getid3_monkey +class getid3_monkey extends getid3_handler { - function getid3_monkey(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; + // based loosely on code from TMonkey by Jurgen Faul // http://jfaul.de/atl or http://j-faul.virtualave.net/atl/atl.html - $ThisFileInfo['fileformat'] = 'mac'; - $ThisFileInfo['audio']['dataformat'] = 'mac'; - $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; - $ThisFileInfo['audio']['lossless'] = true; + $info['fileformat'] = 'mac'; + $info['audio']['dataformat'] = 'mac'; + $info['audio']['bitrate_mode'] = 'vbr'; + $info['audio']['lossless'] = true; - $ThisFileInfo['monkeys_audio']['raw'] = array(); - $thisfile_monkeysaudio = &$ThisFileInfo['monkeys_audio']; + $info['monkeys_audio']['raw'] = array(); + $thisfile_monkeysaudio = &$info['monkeys_audio']; $thisfile_monkeysaudio_raw = &$thisfile_monkeysaudio['raw']; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $MACheaderData = fread($fd, 74); + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $MACheaderData = fread($this->getid3->fp, 74); $thisfile_monkeysaudio_raw['magic'] = substr($MACheaderData, 0, 4); - if ($thisfile_monkeysaudio_raw['magic'] != 'MAC ') { - $ThisFileInfo['error'][] = 'Expecting "MAC" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$thisfile_monkeysaudio_raw['magic'].'"'; - unset($ThisFileInfo['fileformat']); + $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+ @@ -105,13 +108,13 @@ class getid3_monkey } $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']; - $ThisFileInfo['audio']['channels'] = $thisfile_monkeysaudio['channels']; + $info['audio']['channels'] = $thisfile_monkeysaudio['channels']; $thisfile_monkeysaudio['sample_rate'] = $thisfile_monkeysaudio_raw['nSampleRate']; if ($thisfile_monkeysaudio['sample_rate'] == 0) { - $ThisFileInfo['error'][] = 'Corrupt MAC file: frequency == zero'; + $info['error'][] = 'Corrupt MAC file: frequency == zero'; return false; } - $ThisFileInfo['audio']['sample_rate'] = $thisfile_monkeysaudio['sample_rate']; + $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); @@ -123,52 +126,52 @@ class getid3_monkey } $thisfile_monkeysaudio['playtime'] = $thisfile_monkeysaudio['samples'] / $thisfile_monkeysaudio['sample_rate']; if ($thisfile_monkeysaudio['playtime'] == 0) { - $ThisFileInfo['error'][] = 'Corrupt MAC file: playtime == zero'; + $info['error'][] = 'Corrupt MAC file: playtime == zero'; return false; } - $ThisFileInfo['playtime_seconds'] = $thisfile_monkeysaudio['playtime']; - $thisfile_monkeysaudio['compressed_size'] = $ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']; + $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) { - $ThisFileInfo['error'][] = 'Corrupt MAC file: uncompressed_size == zero'; + $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']; - $ThisFileInfo['audio']['bitrate'] = $thisfile_monkeysaudio['bitrate']; + $info['audio']['bitrate'] = $thisfile_monkeysaudio['bitrate']; // add size of MAC header to avdataoffset if ($thisfile_monkeysaudio_raw['nVersion'] >= 3980) { - $ThisFileInfo['avdataoffset'] += $thisfile_monkeysaudio_raw['nDescriptorBytes']; - $ThisFileInfo['avdataoffset'] += $thisfile_monkeysaudio_raw['nHeaderBytes']; - $ThisFileInfo['avdataoffset'] += $thisfile_monkeysaudio_raw['nSeekTableBytes']; - $ThisFileInfo['avdataoffset'] += $thisfile_monkeysaudio_raw['nHeaderDataBytes']; + $info['avdataoffset'] += $thisfile_monkeysaudio_raw['nDescriptorBytes']; + $info['avdataoffset'] += $thisfile_monkeysaudio_raw['nHeaderBytes']; + $info['avdataoffset'] += $thisfile_monkeysaudio_raw['nSeekTableBytes']; + $info['avdataoffset'] += $thisfile_monkeysaudio_raw['nHeaderDataBytes']; - $ThisFileInfo['avdataend'] -= $thisfile_monkeysaudio_raw['nTerminatingDataBytes']; + $info['avdataend'] -= $thisfile_monkeysaudio_raw['nTerminatingDataBytes']; } else { - $ThisFileInfo['avdataoffset'] += $offset; + $info['avdataoffset'] += $offset; } if ($thisfile_monkeysaudio_raw['nVersion'] >= 3980) { if ($thisfile_monkeysaudio_raw['cFileMD5'] === str_repeat("\x00", 16)) { - //$ThisFileInfo['warning'][] = 'cFileMD5 is null'; + //$info['warning'][] = 'cFileMD5 is null'; } else { - $ThisFileInfo['md5_data_source'] = ''; + $info['md5_data_source'] = ''; $md5 = $thisfile_monkeysaudio_raw['cFileMD5']; for ($i = 0; $i < strlen($md5); $i++) { - $ThisFileInfo['md5_data_source'] .= str_pad(dechex(ord($md5{$i})), 2, '00', STR_PAD_LEFT); + $info['md5_data_source'] .= str_pad(dechex(ord($md5{$i})), 2, '00', STR_PAD_LEFT); } - if (!preg_match('/^[0-9a-f]{32}$/', $ThisFileInfo['md5_data_source'])) { - unset($ThisFileInfo['md5_data_source']); + if (!preg_match('/^[0-9a-f]{32}$/', $info['md5_data_source'])) { + unset($info['md5_data_source']); } } } - $ThisFileInfo['audio']['bits_per_sample'] = $thisfile_monkeysaudio['bits_per_sample']; - $ThisFileInfo['audio']['encoder'] = 'MAC v'.number_format($thisfile_monkeysaudio['version'], 2); - $ThisFileInfo['audio']['encoder_options'] = ucfirst($thisfile_monkeysaudio['compression']).' compression'; + $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; } diff --git a/apps/media/getID3/getid3/module.audio.mp3.php b/3rdparty/getid3/module.audio.mp3.php similarity index 72% rename from apps/media/getID3/getid3/module.audio.mp3.php rename to 3rdparty/getid3/module.audio.mp3.php index ac9a27380e..909646e1a1 100644 --- a/apps/media/getID3/getid3/module.audio.mp3.php +++ b/3rdparty/getid3/module.audio.mp3.php @@ -21,65 +21,70 @@ define('GETID3_MP3_VALID_CHECK_FRAMES', 35); -class getid3_mp3 +class getid3_mp3 extends getid3_handler { var $allow_bruteforce = false; // forces getID3() to scan the file byte-by-byte and log all the valid audio frame headers - extremely slow, unrecommended, but may provide data from otherwise-unusuable files - function getid3_mp3(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - if (!$this->getOnlyMPEGaudioInfo($fd, $ThisFileInfo, $ThisFileInfo['avdataoffset'])) { + $initialOffset = $info['avdataoffset']; + + if (!$this->getOnlyMPEGaudioInfo($info['avdataoffset'])) { if ($this->allow_bruteforce) { - $ThisFileInfo['error'][] = 'Rescanning file in BruteForce mode'; - $this->getOnlyMPEGaudioInfoBruteForce($fd, $ThisFileInfo); + $info['error'][] = 'Rescanning file in BruteForce mode'; + $this->getOnlyMPEGaudioInfoBruteForce($this->getid3->fp, $info); } } - if (isset($ThisFileInfo['mpeg']['audio']['bitrate_mode'])) { - $ThisFileInfo['audio']['bitrate_mode'] = strtolower($ThisFileInfo['mpeg']['audio']['bitrate_mode']); + if (isset($info['mpeg']['audio']['bitrate_mode'])) { + $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); } - if (((isset($ThisFileInfo['id3v2']['headerlength']) && ($ThisFileInfo['avdataoffset'] > $ThisFileInfo['id3v2']['headerlength'])) || (!isset($ThisFileInfo['id3v2']) && ($ThisFileInfo['avdataoffset'] > 0)))) { + if (((isset($info['id3v2']['headerlength']) && ($info['avdataoffset'] > $info['id3v2']['headerlength'])) || (!isset($info['id3v2']) && ($info['avdataoffset'] > 0) && ($info['avdataoffset'] != $initialOffset)))) { $synchoffsetwarning = 'Unknown data before synch '; - if (isset($ThisFileInfo['id3v2']['headerlength'])) { - $synchoffsetwarning .= '(ID3v2 header ends at '.$ThisFileInfo['id3v2']['headerlength'].', then '.($ThisFileInfo['avdataoffset'] - $ThisFileInfo['id3v2']['headerlength']).' bytes garbage, '; + if (isset($info['id3v2']['headerlength'])) { + $synchoffsetwarning .= '(ID3v2 header ends at '.$info['id3v2']['headerlength'].', then '.($info['avdataoffset'] - $info['id3v2']['headerlength']).' bytes garbage, '; + } elseif ($initialOffset > 0) { + $synchoffsetwarning .= '(should be at '.$initialOffset.', '; } else { $synchoffsetwarning .= '(should be at beginning of file, '; } - $synchoffsetwarning .= 'synch detected at '.$ThisFileInfo['avdataoffset'].')'; - if (@$ThisFileInfo['audio']['bitrate_mode'] == 'cbr') { + $synchoffsetwarning .= 'synch detected at '.$info['avdataoffset'].')'; + if (isset($info['audio']['bitrate_mode']) && ($info['audio']['bitrate_mode'] == 'cbr')) { - if (!empty($ThisFileInfo['id3v2']['headerlength']) && (($ThisFileInfo['avdataoffset'] - $ThisFileInfo['id3v2']['headerlength']) == $ThisFileInfo['mpeg']['audio']['framelength'])) { + if (!empty($info['id3v2']['headerlength']) && (($info['avdataoffset'] - $info['id3v2']['headerlength']) == $info['mpeg']['audio']['framelength'])) { $synchoffsetwarning .= '. This is a known problem with some versions of LAME (3.90-3.92) DLL in CBR mode.'; - $ThisFileInfo['audio']['codec'] = 'LAME'; + $info['audio']['codec'] = 'LAME'; $CurrentDataLAMEversionString = 'LAME3.'; - } elseif (empty($ThisFileInfo['id3v2']['headerlength']) && ($ThisFileInfo['avdataoffset'] == $ThisFileInfo['mpeg']['audio']['framelength'])) { + } elseif (empty($info['id3v2']['headerlength']) && ($info['avdataoffset'] == $info['mpeg']['audio']['framelength'])) { $synchoffsetwarning .= '. This is a known problem with some versions of LAME (3.90 - 3.92) DLL in CBR mode.'; - $ThisFileInfo['audio']['codec'] = 'LAME'; + $info['audio']['codec'] = 'LAME'; $CurrentDataLAMEversionString = 'LAME3.'; } } - $ThisFileInfo['warning'][] = $synchoffsetwarning; + $info['warning'][] = $synchoffsetwarning; } - if (isset($ThisFileInfo['mpeg']['audio']['LAME'])) { - $ThisFileInfo['audio']['codec'] = 'LAME'; - if (!empty($ThisFileInfo['mpeg']['audio']['LAME']['long_version'])) { - $ThisFileInfo['audio']['encoder'] = rtrim($ThisFileInfo['mpeg']['audio']['LAME']['long_version'], "\x00"); - } elseif (!empty($ThisFileInfo['mpeg']['audio']['LAME']['short_version'])) { - $ThisFileInfo['audio']['encoder'] = rtrim($ThisFileInfo['mpeg']['audio']['LAME']['short_version'], "\x00"); + if (isset($info['mpeg']['audio']['LAME'])) { + $info['audio']['codec'] = 'LAME'; + if (!empty($info['mpeg']['audio']['LAME']['long_version'])) { + $info['audio']['encoder'] = rtrim($info['mpeg']['audio']['LAME']['long_version'], "\x00"); + } elseif (!empty($info['mpeg']['audio']['LAME']['short_version'])) { + $info['audio']['encoder'] = rtrim($info['mpeg']['audio']['LAME']['short_version'], "\x00"); } } - $CurrentDataLAMEversionString = (!empty($CurrentDataLAMEversionString) ? $CurrentDataLAMEversionString : @$ThisFileInfo['audio']['encoder']); + $CurrentDataLAMEversionString = (!empty($CurrentDataLAMEversionString) ? $CurrentDataLAMEversionString : (isset($info['audio']['encoder']) ? $info['audio']['encoder'] : '')); if (!empty($CurrentDataLAMEversionString) && (substr($CurrentDataLAMEversionString, 0, 6) == 'LAME3.') && !preg_match('[0-9\)]', substr($CurrentDataLAMEversionString, -1))) { // a version number of LAME that does not end with a number like "LAME3.92" // or with a closing parenthesis like "LAME3.88 (alpha)" @@ -89,9 +94,9 @@ class getid3_mp3 $PossiblyLongerLAMEversion_FrameLength = 1441; // Not sure what version of LAME this is - look in padding of last frame for longer version string - $PossibleLAMEversionStringOffset = $ThisFileInfo['avdataend'] - $PossiblyLongerLAMEversion_FrameLength; - fseek($fd, $PossibleLAMEversionStringOffset); - $PossiblyLongerLAMEversion_Data = fread($fd, $PossiblyLongerLAMEversion_FrameLength); + $PossibleLAMEversionStringOffset = $info['avdataend'] - $PossiblyLongerLAMEversion_FrameLength; + fseek($this->getid3->fp, $PossibleLAMEversionStringOffset); + $PossiblyLongerLAMEversion_Data = fread($this->getid3->fp, $PossiblyLongerLAMEversion_FrameLength); switch (substr($CurrentDataLAMEversionString, -1)) { case 'a': case 'b': @@ -103,62 +108,63 @@ class getid3_mp3 if (($PossiblyLongerLAMEversion_String = strstr($PossiblyLongerLAMEversion_Data, $CurrentDataLAMEversionString)) !== false) { if (substr($PossiblyLongerLAMEversion_String, 0, strlen($CurrentDataLAMEversionString)) == $CurrentDataLAMEversionString) { $PossiblyLongerLAMEversion_NewString = substr($PossiblyLongerLAMEversion_String, 0, strspn($PossiblyLongerLAMEversion_String, 'LAME0123456789., (abcdefghijklmnopqrstuvwxyzJFSOND)')); //"LAME3.90.3" "LAME3.87 (beta 1, Sep 27 2000)" "LAME3.88 (beta)" - if (strlen($PossiblyLongerLAMEversion_NewString) > strlen(@$ThisFileInfo['audio']['encoder'])) { - $ThisFileInfo['audio']['encoder'] = $PossiblyLongerLAMEversion_NewString; + if (empty($info['audio']['encoder']) || (strlen($PossiblyLongerLAMEversion_NewString) > strlen($info['audio']['encoder']))) { + $info['audio']['encoder'] = $PossiblyLongerLAMEversion_NewString; } } } } - if (!empty($ThisFileInfo['audio']['encoder'])) { - $ThisFileInfo['audio']['encoder'] = rtrim($ThisFileInfo['audio']['encoder'], "\x00 "); + if (!empty($info['audio']['encoder'])) { + $info['audio']['encoder'] = rtrim($info['audio']['encoder'], "\x00 "); } - switch (@$ThisFileInfo['mpeg']['audio']['layer']) { + switch (isset($info['mpeg']['audio']['layer']) ? $info['mpeg']['audio']['layer'] : '') { case 1: case 2: - $ThisFileInfo['audio']['dataformat'] = 'mp'.$ThisFileInfo['mpeg']['audio']['layer']; + $info['audio']['dataformat'] = 'mp'.$info['mpeg']['audio']['layer']; break; } - if (@$ThisFileInfo['fileformat'] == 'mp3') { - switch ($ThisFileInfo['audio']['dataformat']) { + if (isset($info['fileformat']) && ($info['fileformat'] == 'mp3')) { + switch ($info['audio']['dataformat']) { case 'mp1': case 'mp2': case 'mp3': - $ThisFileInfo['fileformat'] = $ThisFileInfo['audio']['dataformat']; + $info['fileformat'] = $info['audio']['dataformat']; break; default: - $ThisFileInfo['warning'][] = 'Expecting [audio][dataformat] to be mp1/mp2/mp3 when fileformat == mp3, [audio][dataformat] actually "'.$ThisFileInfo['audio']['dataformat'].'"'; + $info['warning'][] = 'Expecting [audio][dataformat] to be mp1/mp2/mp3 when fileformat == mp3, [audio][dataformat] actually "'.$info['audio']['dataformat'].'"'; break; } } - if (empty($ThisFileInfo['fileformat'])) { - unset($ThisFileInfo['fileformat']); - unset($ThisFileInfo['audio']['bitrate_mode']); - unset($ThisFileInfo['avdataoffset']); - unset($ThisFileInfo['avdataend']); + if (empty($info['fileformat'])) { + unset($info['fileformat']); + unset($info['audio']['bitrate_mode']); + unset($info['avdataoffset']); + unset($info['avdataend']); return false; } - $ThisFileInfo['mime_type'] = 'audio/mpeg'; - $ThisFileInfo['audio']['lossless'] = false; + $info['mime_type'] = 'audio/mpeg'; + $info['audio']['lossless'] = false; // Calculate playtime - if (!isset($ThisFileInfo['playtime_seconds']) && isset($ThisFileInfo['audio']['bitrate']) && ($ThisFileInfo['audio']['bitrate'] > 0)) { - $ThisFileInfo['playtime_seconds'] = ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8 / $ThisFileInfo['audio']['bitrate']; + if (!isset($info['playtime_seconds']) && isset($info['audio']['bitrate']) && ($info['audio']['bitrate'] > 0)) { + $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['audio']['bitrate']; } - $ThisFileInfo['audio']['encoder_options'] = $this->GuessEncoderOptions($ThisFileInfo); + $info['audio']['encoder_options'] = $this->GuessEncoderOptions(); return true; } - function GuessEncoderOptions(&$ThisFileInfo) { + function GuessEncoderOptions() { // shortcuts - if (!empty($ThisFileInfo['mpeg']['audio'])) { - $thisfile_mpeg_audio = &$ThisFileInfo['mpeg']['audio']; + $info = &$this->getid3->info; + if (!empty($info['mpeg']['audio'])) { + $thisfile_mpeg_audio = &$info['mpeg']['audio']; if (!empty($thisfile_mpeg_audio['LAME'])) { $thisfile_mpeg_audio_lame = &$thisfile_mpeg_audio['LAME']; } @@ -167,7 +173,7 @@ class getid3_mp3 $encoder_options = ''; static $NamedPresetBitrates = array(16, 24, 40, 56, 112, 128, 160, 192, 256); - if ((@$thisfile_mpeg_audio['VBR_method'] == 'Fraunhofer') && !empty($thisfile_mpeg_audio['VBR_quality'])) { + if (isset($thisfile_mpeg_audio['VBR_method']) && ($thisfile_mpeg_audio['VBR_method'] == 'Fraunhofer') && !empty($thisfile_mpeg_audio['VBR_quality'])) { $encoder_options = 'VBR q'.$thisfile_mpeg_audio['VBR_quality']; @@ -242,7 +248,7 @@ class getid3_mp3 $encoder_options = $KnownEncoderValues['**'][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']]; - } elseif ($ThisFileInfo['audio']['bitrate_mode'] == 'vbr') { + } elseif ($info['audio']['bitrate_mode'] == 'vbr') { // http://gabriel.mp3-tech.org/mp3infotag.html // int Quality = (100 - 10 * gfp->VBR_q - gfp->quality)h @@ -252,13 +258,13 @@ class getid3_mp3 $LAME_q_value = 100 - $thisfile_mpeg_audio_lame['vbr_quality'] - ($LAME_V_value * 10); $encoder_options = '-V'.$LAME_V_value.' -q'.$LAME_q_value; - } elseif ($ThisFileInfo['audio']['bitrate_mode'] == 'cbr') { + } elseif ($info['audio']['bitrate_mode'] == 'cbr') { - $encoder_options = strtoupper($ThisFileInfo['audio']['bitrate_mode']).ceil($ThisFileInfo['audio']['bitrate'] / 1000); + $encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000); } else { - $encoder_options = strtoupper($ThisFileInfo['audio']['bitrate_mode']); + $encoder_options = strtoupper($info['audio']['bitrate_mode']); } @@ -266,12 +272,12 @@ class getid3_mp3 $encoder_options = 'ABR'.$thisfile_mpeg_audio_lame['bitrate_abr']; - } elseif (!empty($ThisFileInfo['audio']['bitrate'])) { + } elseif (!empty($info['audio']['bitrate'])) { - if ($ThisFileInfo['audio']['bitrate_mode'] == 'cbr') { - $encoder_options = strtoupper($ThisFileInfo['audio']['bitrate_mode']).ceil($ThisFileInfo['audio']['bitrate'] / 1000); + if ($info['audio']['bitrate_mode'] == 'cbr') { + $encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000); } else { - $encoder_options = strtoupper($ThisFileInfo['audio']['bitrate_mode']); + $encoder_options = strtoupper($info['audio']['bitrate_mode']); } } @@ -279,7 +285,7 @@ class getid3_mp3 $encoder_options .= ' -b'.$thisfile_mpeg_audio_lame['bitrate_min']; } - if (@$thisfile_mpeg_audio_lame['encoding_flags']['nogap_prev'] || @$thisfile_mpeg_audio_lame['encoding_flags']['nogap_next']) { + if (!empty($thisfile_mpeg_audio_lame['encoding_flags']['nogap_prev']) || !empty($thisfile_mpeg_audio_lame['encoding_flags']['nogap_next'])) { $encoder_options .= ' --nogap'; } @@ -389,17 +395,16 @@ class getid3_mp3 } } } - if (empty($encoder_options) && !empty($ThisFileInfo['audio']['bitrate']) && !empty($ThisFileInfo['audio']['bitrate_mode'])) { - //$encoder_options = strtoupper($ThisFileInfo['audio']['bitrate_mode']).ceil($ThisFileInfo['audio']['bitrate'] / 1000); - $encoder_options = strtoupper($ThisFileInfo['audio']['bitrate_mode']); + if (empty($encoder_options) && !empty($info['audio']['bitrate']) && !empty($info['audio']['bitrate_mode'])) { + //$encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000); + $encoder_options = strtoupper($info['audio']['bitrate_mode']); } return $encoder_options; } - function decodeMPEGaudioHeader($fd, $offset, &$ThisFileInfo, $recursivesearch=true, $ScanAsCBR=false, $FastMPEGheaderScan=false) { - + function decodeMPEGaudioHeader($offset, &$info, $recursivesearch=true, $ScanAsCBR=false, $FastMPEGheaderScan=false) { static $MPEGaudioVersionLookup; static $MPEGaudioLayerLookup; static $MPEGaudioBitrateLookup; @@ -417,13 +422,12 @@ class getid3_mp3 $MPEGaudioEmphasisLookup = getid3_mp3::MPEGaudioEmphasisArray(); } - if ($offset >= $ThisFileInfo['avdataend']) { - $ThisFileInfo['error'][] = 'end of file encounter looking for MPEG synch'; + if (fseek($this->getid3->fp, $offset, SEEK_SET) != 0) { + $info['error'][] = 'decodeMPEGaudioHeader() failed to seek to next offset at '.$offset; return false; } - fseek($fd, $offset, SEEK_SET); - //$headerstring = fread($fd, 1441); // worst-case max length = 32kHz @ 320kbps layer 3 = 1441 bytes/frame - $headerstring = fread($fd, 226); // LAME header at offset 36 + 190 bytes of Xing/LAME data + //$headerstring = fread($this->getid3->fp, 1441); // worst-case max length = 32kHz @ 320kbps layer 3 = 1441 bytes/frame + $headerstring = fread($this->getid3->fp, 226); // LAME header at offset 36 + 190 bytes of Xing/LAME data // MP3 audio frame structure: // $aa $aa $aa $aa [$bb $bb] $cc... @@ -442,29 +446,26 @@ class getid3_mp3 } static $MPEGaudioHeaderValidCache = array(); - - // Not in cache - if (!isset($MPEGaudioHeaderValidCache[$head4])) { + if (!isset($MPEGaudioHeaderValidCache[$head4])) { // Not in cache //$MPEGaudioHeaderValidCache[$head4] = getid3_mp3::MPEGaudioHeaderValid($MPEGheaderRawArray, false, true); // allow badly-formatted freeformat (from LAME 3.90 - 3.93.1) $MPEGaudioHeaderValidCache[$head4] = getid3_mp3::MPEGaudioHeaderValid($MPEGheaderRawArray, false, false); } // shortcut - if (!isset($ThisFileInfo['mpeg']['audio'])) { - $ThisFileInfo['mpeg']['audio'] = array(); + if (!isset($info['mpeg']['audio'])) { + $info['mpeg']['audio'] = array(); } - $thisfile_mpeg_audio = &$ThisFileInfo['mpeg']['audio']; + $thisfile_mpeg_audio = &$info['mpeg']['audio']; if ($MPEGaudioHeaderValidCache[$head4]) { $thisfile_mpeg_audio['raw'] = $MPEGheaderRawArray; } else { - $ThisFileInfo['error'][] = 'Invalid MPEG audio header at offset '.$offset; + $info['error'][] = 'Invalid MPEG audio header ('.getid3_lib::PrintHexBytes($head4).') at offset '.$offset; return false; } if (!$FastMPEGheaderScan) { - $thisfile_mpeg_audio['version'] = $MPEGaudioVersionLookup[$thisfile_mpeg_audio['raw']['version']]; $thisfile_mpeg_audio['layer'] = $MPEGaudioLayerLookup[$thisfile_mpeg_audio['raw']['layer']]; @@ -478,24 +479,23 @@ class getid3_mp3 $thisfile_mpeg_audio['original'] = (bool) $thisfile_mpeg_audio['raw']['original']; $thisfile_mpeg_audio['emphasis'] = $MPEGaudioEmphasisLookup[$thisfile_mpeg_audio['raw']['emphasis']]; - $ThisFileInfo['audio']['channels'] = $thisfile_mpeg_audio['channels']; - $ThisFileInfo['audio']['sample_rate'] = $thisfile_mpeg_audio['sample_rate']; + $info['audio']['channels'] = $thisfile_mpeg_audio['channels']; + $info['audio']['sample_rate'] = $thisfile_mpeg_audio['sample_rate']; if ($thisfile_mpeg_audio['protection']) { $thisfile_mpeg_audio['crc'] = getid3_lib::BigEndian2Int(substr($headerstring, 4, 2)); } - } if ($thisfile_mpeg_audio['raw']['bitrate'] == 15) { // http://www.hydrogenaudio.org/?act=ST&f=16&t=9682&st=0 - $ThisFileInfo['warning'][] = 'Invalid bitrate index (15), this is a known bug in free-format MP3s encoded by LAME v3.90 - 3.93.1'; + $info['warning'][] = 'Invalid bitrate index (15), this is a known bug in free-format MP3s encoded by LAME v3.90 - 3.93.1'; $thisfile_mpeg_audio['raw']['bitrate'] = 0; } $thisfile_mpeg_audio['padding'] = (bool) $thisfile_mpeg_audio['raw']['padding']; $thisfile_mpeg_audio['bitrate'] = $MPEGaudioBitrateLookup[$thisfile_mpeg_audio['version']][$thisfile_mpeg_audio['layer']][$thisfile_mpeg_audio['raw']['bitrate']]; - if (($thisfile_mpeg_audio['bitrate'] == 'free') && ($offset == $ThisFileInfo['avdataoffset'])) { + if (($thisfile_mpeg_audio['bitrate'] == 'free') && ($offset == $info['avdataoffset'])) { // only skip multiple frame check if free-format bitstream found at beginning of file // otherwise is quite possibly simply corrupted data $recursivesearch = false; @@ -504,14 +504,14 @@ class getid3_mp3 // For Layer 2 there are some combinations of bitrate and mode which are not allowed. if (!$FastMPEGheaderScan && ($thisfile_mpeg_audio['layer'] == '2')) { - $ThisFileInfo['audio']['dataformat'] = 'mp2'; + $info['audio']['dataformat'] = 'mp2'; switch ($thisfile_mpeg_audio['channelmode']) { case 'mono': if (($thisfile_mpeg_audio['bitrate'] == 'free') || ($thisfile_mpeg_audio['bitrate'] <= 192000)) { // these are ok } else { - $ThisFileInfo['error'][] = $thisfile_mpeg_audio['bitrate'].'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.'; + $info['error'][] = $thisfile_mpeg_audio['bitrate'].'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.'; return false; } break; @@ -522,7 +522,7 @@ class getid3_mp3 if (($thisfile_mpeg_audio['bitrate'] == 'free') || ($thisfile_mpeg_audio['bitrate'] == 64000) || ($thisfile_mpeg_audio['bitrate'] >= 96000)) { // these are ok } else { - $ThisFileInfo['error'][] = intval(round($thisfile_mpeg_audio['bitrate'] / 1000)).'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.'; + $info['error'][] = intval(round($thisfile_mpeg_audio['bitrate'] / 1000)).'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.'; return false; } break; @@ -532,19 +532,19 @@ class getid3_mp3 } - if ($ThisFileInfo['audio']['sample_rate'] > 0) { - $thisfile_mpeg_audio['framelength'] = getid3_mp3::MPEGaudioFrameLength($thisfile_mpeg_audio['bitrate'], $thisfile_mpeg_audio['version'], $thisfile_mpeg_audio['layer'], (int) $thisfile_mpeg_audio['padding'], $ThisFileInfo['audio']['sample_rate']); + if ($info['audio']['sample_rate'] > 0) { + $thisfile_mpeg_audio['framelength'] = getid3_mp3::MPEGaudioFrameLength($thisfile_mpeg_audio['bitrate'], $thisfile_mpeg_audio['version'], $thisfile_mpeg_audio['layer'], (int) $thisfile_mpeg_audio['padding'], $info['audio']['sample_rate']); } $nextframetestoffset = $offset + 1; if ($thisfile_mpeg_audio['bitrate'] != 'free') { - $ThisFileInfo['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate']; + $info['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate']; if (isset($thisfile_mpeg_audio['framelength'])) { $nextframetestoffset = $offset + $thisfile_mpeg_audio['framelength']; } else { - $ThisFileInfo['error'][] = 'Frame at offset('.$offset.') is has an invalid frame length.'; + $info['error'][] = 'Frame at offset('.$offset.') is has an invalid frame length.'; return false; } @@ -561,7 +561,7 @@ class getid3_mp3 $thisfile_mpeg_audio['bitrate_mode'] = 'vbr'; $thisfile_mpeg_audio['VBR_method'] = 'Fraunhofer'; - $ThisFileInfo['audio']['codec'] = 'Fraunhofer'; + $info['audio']['codec'] = 'Fraunhofer'; $SideInfoData = substr($headerstring, 4 + 2, 32); @@ -653,12 +653,12 @@ class getid3_mp3 if ($thisfile_mpeg_audio['layer'] == '1') { // BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12 - //$ThisFileInfo['audio']['bitrate'] = ((($framelengthfloat / 4) - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 12; - $ThisFileInfo['audio']['bitrate'] = ($framelengthfloat / 4) * $thisfile_mpeg_audio['sample_rate'] * (2 / $ThisFileInfo['audio']['channels']) / 12; + //$info['audio']['bitrate'] = ((($framelengthfloat / 4) - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 12; + $info['audio']['bitrate'] = ($framelengthfloat / 4) * $thisfile_mpeg_audio['sample_rate'] * (2 / $info['audio']['channels']) / 12; } else { // Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144 - //$ThisFileInfo['audio']['bitrate'] = (($framelengthfloat - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 144; - $ThisFileInfo['audio']['bitrate'] = $framelengthfloat * $thisfile_mpeg_audio['sample_rate'] * (2 / $ThisFileInfo['audio']['channels']) / 144; + //$info['audio']['bitrate'] = (($framelengthfloat - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 144; + $info['audio']['bitrate'] = $framelengthfloat * $thisfile_mpeg_audio['sample_rate'] * (2 / $info['audio']['channels']) / 144; } $thisfile_mpeg_audio['framelength'] = floor($framelengthfloat); } @@ -759,10 +759,10 @@ class getid3_mp3 $thisfile_mpeg_audio_lame_RGAD_track['gain_db'] = getid3_lib::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_track['raw']['sign_bit']); if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) { - $ThisFileInfo['replay_gain']['track']['peak'] = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude']; + $info['replay_gain']['track']['peak'] = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude']; } - $ThisFileInfo['replay_gain']['track']['originator'] = $thisfile_mpeg_audio_lame_RGAD_track['originator']; - $ThisFileInfo['replay_gain']['track']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_track['gain_db']; + $info['replay_gain']['track']['originator'] = $thisfile_mpeg_audio_lame_RGAD_track['originator']; + $info['replay_gain']['track']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_track['gain_db']; } else { unset($thisfile_mpeg_audio_lame_RGAD['track']); } @@ -777,10 +777,10 @@ class getid3_mp3 $thisfile_mpeg_audio_lame_RGAD_album['gain_db'] = getid3_lib::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_album['raw']['sign_bit']); if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) { - $ThisFileInfo['replay_gain']['album']['peak'] = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude']; + $info['replay_gain']['album']['peak'] = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude']; } - $ThisFileInfo['replay_gain']['album']['originator'] = $thisfile_mpeg_audio_lame_RGAD_album['originator']; - $ThisFileInfo['replay_gain']['album']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_album['gain_db']; + $info['replay_gain']['album']['originator'] = $thisfile_mpeg_audio_lame_RGAD_album['originator']; + $info['replay_gain']['album']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_album['gain_db']; } else { unset($thisfile_mpeg_audio_lame_RGAD['album']); } @@ -836,7 +836,7 @@ class getid3_mp3 $thisfile_mpeg_audio_lame['preset_used_id'] = ($PresetSurroundBytes & 0x07FF); $thisfile_mpeg_audio_lame['preset_used'] = getid3_mp3::LAMEpresetUsedLookup($thisfile_mpeg_audio_lame); if (!empty($thisfile_mpeg_audio_lame['preset_used_id']) && empty($thisfile_mpeg_audio_lame['preset_used'])) { - $ThisFileInfo['warning'][] = 'Unknown LAME preset used ('.$thisfile_mpeg_audio_lame['preset_used_id'].') - please report to info@getid3.org'; + $info['warning'][] = 'Unknown LAME preset used ('.$thisfile_mpeg_audio_lame['preset_used_id'].') - please report to info@getid3.org'; } if (($thisfile_mpeg_audio_lame['short_version'] == 'LAME3.90.') && !empty($thisfile_mpeg_audio_lame['preset_used_id'])) { // this may change if 3.90.4 ever comes out @@ -859,7 +859,7 @@ class getid3_mp3 $thisfile_mpeg_audio['bitrate_mode'] = 'cbr'; $thisfile_mpeg_audio['bitrate'] = getid3_mp3::ClosestStandardMP3Bitrate($thisfile_mpeg_audio['bitrate']); - $ThisFileInfo['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate']; + $info['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate']; //if (empty($thisfile_mpeg_audio['bitrate']) || (!empty($thisfile_mpeg_audio_lame['bitrate_min']) && ($thisfile_mpeg_audio_lame['bitrate_min'] != 255))) { // $thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio_lame['bitrate_min']; //} @@ -875,12 +875,12 @@ class getid3_mp3 $thisfile_mpeg_audio['bitrate_mode'] = 'cbr'; if ($recursivesearch) { $thisfile_mpeg_audio['bitrate_mode'] = 'vbr'; - if (getid3_mp3::RecursiveFrameScanning($fd, $ThisFileInfo, $offset, $nextframetestoffset, true)) { + if ($this->RecursiveFrameScanning($offset, $nextframetestoffset, true)) { $recursivesearch = false; $thisfile_mpeg_audio['bitrate_mode'] = 'cbr'; } if ($thisfile_mpeg_audio['bitrate_mode'] == 'vbr') { - $ThisFileInfo['warning'][] = 'VBR file with no VBR header. Bitrate values calculated from actual frame bitrates.'; + $info['warning'][] = 'VBR file with no VBR header. Bitrate values calculated from actual frame bitrates.'; } } @@ -888,64 +888,64 @@ class getid3_mp3 } - if (($ExpectedNumberOfAudioBytes > 0) && ($ExpectedNumberOfAudioBytes != ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']))) { - if ($ExpectedNumberOfAudioBytes > ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'])) { - if (@$ThisFileInfo['fileformat'] == 'riff') { + if (($ExpectedNumberOfAudioBytes > 0) && ($ExpectedNumberOfAudioBytes != ($info['avdataend'] - $info['avdataoffset']))) { + if ($ExpectedNumberOfAudioBytes > ($info['avdataend'] - $info['avdataoffset'])) { + if (isset($info['fileformat']) && ($info['fileformat'] == 'riff')) { // ignore, audio data is broken into chunks so will always be data "missing" - } elseif (($ExpectedNumberOfAudioBytes - ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'])) == 1) { - $ThisFileInfo['warning'][] = 'Last byte of data truncated (this is a known bug in Meracl ID3 Tag Writer before v1.3.5)'; + } elseif (($ExpectedNumberOfAudioBytes - ($info['avdataend'] - $info['avdataoffset'])) == 1) { + $info['warning'][] = 'Last byte of data truncated (this is a known bug in Meracl ID3 Tag Writer before v1.3.5)'; } else { - $ThisFileInfo['warning'][] = 'Probable truncated file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, only found '.($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']).' (short by '.($ExpectedNumberOfAudioBytes - ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'])).' bytes)'; + $info['warning'][] = 'Probable truncated file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, only found '.($info['avdataend'] - $info['avdataoffset']).' (short by '.($ExpectedNumberOfAudioBytes - ($info['avdataend'] - $info['avdataoffset'])).' bytes)'; } } else { - if ((($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) - $ExpectedNumberOfAudioBytes) == 1) { - // $prenullbytefileoffset = ftell($fd); - // fseek($fd, $ThisFileInfo['avdataend'], SEEK_SET); - // $PossibleNullByte = fread($fd, 1); - // fseek($fd, $prenullbytefileoffset, SEEK_SET); + if ((($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes) == 1) { + // $prenullbytefileoffset = ftell($this->getid3->fp); + // fseek($this->getid3->fp, $info['avdataend'], SEEK_SET); + // $PossibleNullByte = fread($this->getid3->fp, 1); + // fseek($this->getid3->fp, $prenullbytefileoffset, SEEK_SET); // if ($PossibleNullByte === "\x00") { - $ThisFileInfo['avdataend']--; - // $ThisFileInfo['warning'][] = 'Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored'; + $info['avdataend']--; + // $info['warning'][] = 'Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored'; // } else { - // $ThisFileInfo['warning'][] = 'Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']).' ('.(($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)'; + // $info['warning'][] = 'Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($info['avdataend'] - $info['avdataoffset']).' ('.(($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)'; // } } else { - $ThisFileInfo['warning'][] = 'Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']).' ('.(($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)'; + $info['warning'][] = 'Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($info['avdataend'] - $info['avdataoffset']).' ('.(($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)'; } } } - if (($thisfile_mpeg_audio['bitrate'] == 'free') && empty($ThisFileInfo['audio']['bitrate'])) { - if (($offset == $ThisFileInfo['avdataoffset']) && empty($thisfile_mpeg_audio['VBR_frames'])) { - $framebytelength = getid3_mp3::FreeFormatFrameLength($fd, $offset, $ThisFileInfo, true); + if (($thisfile_mpeg_audio['bitrate'] == 'free') && empty($info['audio']['bitrate'])) { + if (($offset == $info['avdataoffset']) && empty($thisfile_mpeg_audio['VBR_frames'])) { + $framebytelength = $this->FreeFormatFrameLength($offset, true); if ($framebytelength > 0) { $thisfile_mpeg_audio['framelength'] = $framebytelength; if ($thisfile_mpeg_audio['layer'] == '1') { // BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12 - $ThisFileInfo['audio']['bitrate'] = ((($framebytelength / 4) - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 12; + $info['audio']['bitrate'] = ((($framebytelength / 4) - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 12; } else { // Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144 - $ThisFileInfo['audio']['bitrate'] = (($framebytelength - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 144; + $info['audio']['bitrate'] = (($framebytelength - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 144; } } else { - $ThisFileInfo['error'][] = 'Error calculating frame length of free-format MP3 without Xing/LAME header'; + $info['error'][] = 'Error calculating frame length of free-format MP3 without Xing/LAME header'; } } } - if (@$thisfile_mpeg_audio['VBR_frames']) { + if (isset($thisfile_mpeg_audio['VBR_frames']) ? $thisfile_mpeg_audio['VBR_frames'] : '') { switch ($thisfile_mpeg_audio['bitrate_mode']) { case 'vbr': case 'abr': + $bytes_per_frame = 1152; if (($thisfile_mpeg_audio['version'] == '1') && ($thisfile_mpeg_audio['layer'] == 1)) { - $thisfile_mpeg_audio['VBR_bitrate'] = ((@$thisfile_mpeg_audio['VBR_bytes'] / $thisfile_mpeg_audio['VBR_frames']) * 8) * ($ThisFileInfo['audio']['sample_rate'] / 384); + $bytes_per_frame = 384; } elseif ((($thisfile_mpeg_audio['version'] == '2') || ($thisfile_mpeg_audio['version'] == '2.5')) && ($thisfile_mpeg_audio['layer'] == 3)) { - $thisfile_mpeg_audio['VBR_bitrate'] = ((@$thisfile_mpeg_audio['VBR_bytes'] / $thisfile_mpeg_audio['VBR_frames']) * 8) * ($ThisFileInfo['audio']['sample_rate'] / 576); - } else { - $thisfile_mpeg_audio['VBR_bitrate'] = ((@$thisfile_mpeg_audio['VBR_bytes'] / $thisfile_mpeg_audio['VBR_frames']) * 8) * ($ThisFileInfo['audio']['sample_rate'] / 1152); + $bytes_per_frame = 576; } + $thisfile_mpeg_audio['VBR_bitrate'] = (isset($thisfile_mpeg_audio['VBR_bytes']) ? (($thisfile_mpeg_audio['VBR_bytes'] / $thisfile_mpeg_audio['VBR_frames']) * 8) * ($info['audio']['sample_rate'] / $bytes_per_frame) : 0); if ($thisfile_mpeg_audio['VBR_bitrate'] > 0) { - $ThisFileInfo['audio']['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate']; + $info['audio']['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate']; $thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate']; // to avoid confusion } break; @@ -957,7 +957,7 @@ class getid3_mp3 if ($recursivesearch) { - if (!getid3_mp3::RecursiveFrameScanning($fd, $ThisFileInfo, $offset, $nextframetestoffset, $ScanAsCBR)) { + if (!$this->RecursiveFrameScanning($offset, $nextframetestoffset, $ScanAsCBR)) { return false; } @@ -997,7 +997,7 @@ class getid3_mp3 // } // // if ($thisfile_mpeg_audio['version'] == '1') { - // for ($channel = 0; $channel < $ThisFileInfo['audio']['channels']; $channel++) { + // for ($channel = 0; $channel < $info['audio']['channels']; $channel++) { // for ($scfsi_band = 0; $scfsi_band < 4; $scfsi_band++) { // $thisfile_mpeg_audio['scfsi'][$channel][$scfsi_band] = substr($SideInfoBitstream, $SideInfoOffset, 1); // $SideInfoOffset += 2; @@ -1005,7 +1005,7 @@ class getid3_mp3 // } // } // for ($granule = 0; $granule < (($thisfile_mpeg_audio['version'] == '1') ? 2 : 1); $granule++) { - // for ($channel = 0; $channel < $ThisFileInfo['audio']['channels']; $channel++) { + // for ($channel = 0; $channel < $info['audio']['channels']; $channel++) { // $thisfile_mpeg_audio['part2_3_length'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 12); // $SideInfoOffset += 12; // $thisfile_mpeg_audio['big_values'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9); @@ -1069,20 +1069,23 @@ class getid3_mp3 return true; } - function RecursiveFrameScanning(&$fd, &$ThisFileInfo, &$offset, &$nextframetestoffset, $ScanAsCBR) { + function RecursiveFrameScanning(&$offset, &$nextframetestoffset, $ScanAsCBR) { + $info = &$this->getid3->info; + $firstframetestarray = array('error'=>'', 'warning'=>'', 'avdataend'=>$info['avdataend'], 'avdataoffset'=>$info['avdataoffset']); + $this->decodeMPEGaudioHeader($offset, $firstframetestarray, false); + for ($i = 0; $i < GETID3_MP3_VALID_CHECK_FRAMES; $i++) { // check next GETID3_MP3_VALID_CHECK_FRAMES frames for validity, to make sure we haven't run across a false synch - if (($nextframetestoffset + 4) >= $ThisFileInfo['avdataend']) { + if (($nextframetestoffset + 4) >= $info['avdataend']) { // end of file return true; } - $nextframetestarray = array('error'=>'', 'warning'=>'', 'avdataend'=>$ThisFileInfo['avdataend'], 'avdataoffset'=>$ThisFileInfo['avdataoffset']); - if (getid3_mp3::decodeMPEGaudioHeader($fd, $nextframetestoffset, $nextframetestarray, false)) { + $nextframetestarray = array('error'=>'', 'warning'=>'', 'avdataend'=>$info['avdataend'], 'avdataoffset'=>$info['avdataoffset']); + if ($this->decodeMPEGaudioHeader($nextframetestoffset, $nextframetestarray, false)) { if ($ScanAsCBR) { - // force CBR mode, used for trying to pick out invalid audio streams with - // valid(?) VBR headers, or VBR streams with no VBR header - if (!isset($nextframetestarray['mpeg']['audio']['bitrate']) || !isset($ThisFileInfo['mpeg']['audio']['bitrate']) || ($nextframetestarray['mpeg']['audio']['bitrate'] != $ThisFileInfo['mpeg']['audio']['bitrate'])) { + // force CBR mode, used for trying to pick out invalid audio streams with valid(?) VBR headers, or VBR streams with no VBR header + if (!isset($nextframetestarray['mpeg']['audio']['bitrate']) || !isset($firstframetestarray['mpeg']['audio']['bitrate']) || ($nextframetestarray['mpeg']['audio']['bitrate'] != $firstframetestarray['mpeg']['audio']['bitrate'])) { return false; } } @@ -1092,14 +1095,19 @@ class getid3_mp3 if (isset($nextframetestarray['mpeg']['audio']['framelength']) && ($nextframetestarray['mpeg']['audio']['framelength'] > 0)) { $nextframetestoffset += $nextframetestarray['mpeg']['audio']['framelength']; } else { - $ThisFileInfo['error'][] = 'Frame at offset ('.$offset.') is has an invalid frame length.'; + $info['error'][] = 'Frame at offset ('.$offset.') is has an invalid frame length.'; return false; } + } elseif (!empty($firstframetestarray['mpeg']['audio']['framelength']) && (($nextframetestoffset + $firstframetestarray['mpeg']['audio']['framelength']) > $info['avdataend'])) { + + // it's not the end of the file, but there's not enough data left for another frame, so assume it's garbage/padding and return OK + return true; + } else { // next frame is not valid, note the error and fail, so scanning can contiue for a valid frame sequence - $ThisFileInfo['error'][] = 'Frame at offset ('.$offset.') is valid, but the next one at ('.$nextframetestoffset.') is not.'; + $info['warning'][] = 'Frame at offset ('.$offset.') is valid, but the next one at ('.$nextframetestoffset.') is not.'; return false; } @@ -1107,9 +1115,11 @@ class getid3_mp3 return true; } - function FreeFormatFrameLength($fd, $offset, &$ThisFileInfo, $deepscan=false) { - fseek($fd, $offset, SEEK_SET); - $MPEGaudioData = fread($fd, 32768); + function FreeFormatFrameLength($offset, $deepscan=false) { + $info = &$this->getid3->info; + + fseek($this->getid3->fp, $offset, SEEK_SET); + $MPEGaudioData = fread($this->getid3->fp, 32768); $SyncPattern1 = substr($MPEGaudioData, 0, 4); // may be different pattern due to padding @@ -1140,12 +1150,12 @@ class getid3_mp3 $framelength = $framelength2; } if (!$framelength) { - $ThisFileInfo['error'][] = 'Cannot find next free-format synch pattern ('.getid3_lib::PrintHexBytes($SyncPattern1).' or '.getid3_lib::PrintHexBytes($SyncPattern2).') after offset '.$offset; + $info['error'][] = 'Cannot find next free-format synch pattern ('.getid3_lib::PrintHexBytes($SyncPattern1).' or '.getid3_lib::PrintHexBytes($SyncPattern2).') after offset '.$offset; return false; } else { - $ThisFileInfo['warning'][] = 'ModeExtension varies between first frame and other frames (known free-format issue in LAME 3.88)'; - $ThisFileInfo['audio']['codec'] = 'LAME'; - $ThisFileInfo['audio']['encoder'] = 'LAME3.88'; + $info['warning'][] = 'ModeExtension varies between first frame and other frames (known free-format issue in LAME 3.88)'; + $info['audio']['codec'] = 'LAME'; + $info['audio']['encoder'] = 'LAME3.88'; $SyncPattern1 = substr($SyncPattern1, 0, 3); $SyncPattern2 = substr($SyncPattern2, 0, 3); } @@ -1155,9 +1165,9 @@ class getid3_mp3 $ActualFrameLengthValues = array(); $nextoffset = $offset + $framelength; - while ($nextoffset < ($ThisFileInfo['avdataend'] - 6)) { - fseek($fd, $nextoffset - 1, SEEK_SET); - $NextSyncPattern = fread($fd, 6); + while ($nextoffset < ($info['avdataend'] - 6)) { + fseek($this->getid3->fp, $nextoffset - 1, SEEK_SET); + $NextSyncPattern = fread($this->getid3->fp, 6); if ((substr($NextSyncPattern, 1, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 1, strlen($SyncPattern2)) == $SyncPattern2)) { // good - found where expected $ActualFrameLengthValues[] = $framelength; @@ -1170,7 +1180,7 @@ class getid3_mp3 $ActualFrameLengthValues[] = ($framelength + 1); $nextoffset++; } else { - $ThisFileInfo['error'][] = 'Did not find expected free-format sync pattern at offset '.$nextoffset; + $info['error'][] = 'Did not find expected free-format sync pattern at offset '.$nextoffset; return false; } $nextoffset += $framelength; @@ -1182,11 +1192,10 @@ class getid3_mp3 return $framelength; } - function getOnlyMPEGaudioInfoBruteForce($fd, &$ThisFileInfo) { - - $MPEGaudioHeaderDecodeCache = array(); - $MPEGaudioHeaderValidCache = array(); - $MPEGaudioHeaderLengthCache = array(); + function getOnlyMPEGaudioInfoBruteForce() { + $MPEGaudioHeaderDecodeCache = array(); + $MPEGaudioHeaderValidCache = array(); + $MPEGaudioHeaderLengthCache = array(); $MPEGaudioVersionLookup = getid3_mp3::MPEGaudioVersionArray(); $MPEGaudioLayerLookup = getid3_mp3::MPEGaudioLayerArray(); $MPEGaudioBitrateLookup = getid3_mp3::MPEGaudioBitrateArray(); @@ -1194,34 +1203,34 @@ class getid3_mp3 $MPEGaudioChannelModeLookup = getid3_mp3::MPEGaudioChannelModeArray(); $MPEGaudioModeExtensionLookup = getid3_mp3::MPEGaudioModeExtensionArray(); $MPEGaudioEmphasisLookup = getid3_mp3::MPEGaudioEmphasisArray(); - $LongMPEGversionLookup = array(); - $LongMPEGlayerLookup = array(); - $LongMPEGbitrateLookup = array(); - $LongMPEGpaddingLookup = array(); - $LongMPEGfrequencyLookup = array(); + $LongMPEGversionLookup = array(); + $LongMPEGlayerLookup = array(); + $LongMPEGbitrateLookup = array(); + $LongMPEGpaddingLookup = array(); + $LongMPEGfrequencyLookup = array(); + $Distribution['bitrate'] = array(); + $Distribution['frequency'] = array(); + $Distribution['layer'] = array(); + $Distribution['version'] = array(); + $Distribution['padding'] = array(); - $Distribution['bitrate'] = array(); - $Distribution['frequency'] = array(); - $Distribution['layer'] = array(); - $Distribution['version'] = array(); - $Distribution['padding'] = array(); - - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $info = &$this->getid3->info; + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $max_frames_scan = 5000; $frames_scanned = 0; - $previousvalidframe = $ThisFileInfo['avdataoffset']; - while (ftell($fd) < $ThisFileInfo['avdataend']) { + $previousvalidframe = $info['avdataoffset']; + while (ftell($this->getid3->fp) < $info['avdataend']) { set_time_limit(30); - $head4 = fread($fd, 4); + $head4 = fread($this->getid3->fp, 4); if (strlen($head4) < 4) { break; } if ($head4{0} != "\xFF") { for ($i = 1; $i < 4; $i++) { if ($head4{$i} == "\xFF") { - fseek($fd, $i - 4, SEEK_CUR); + fseek($this->getid3->fp, $i - 4, SEEK_CUR); continue 2; } } @@ -1249,9 +1258,9 @@ class getid3_mp3 $LongMPEGfrequencyLookup[$head4]); } if ($MPEGaudioHeaderLengthCache[$head4] > 4) { - $WhereWeWere = ftell($fd); - fseek($fd, $MPEGaudioHeaderLengthCache[$head4] - 4, SEEK_CUR); - $next4 = fread($fd, 4); + $WhereWeWere = ftell($this->getid3->fp); + fseek($this->getid3->fp, $MPEGaudioHeaderLengthCache[$head4] - 4, SEEK_CUR); + $next4 = fread($this->getid3->fp, 4); if ($next4{0} == "\xFF") { if (!isset($MPEGaudioHeaderDecodeCache[$next4])) { $MPEGaudioHeaderDecodeCache[$next4] = getid3_mp3::MPEGaudioHeaderDecode($next4); @@ -1260,16 +1269,16 @@ class getid3_mp3 $MPEGaudioHeaderValidCache[$next4] = getid3_mp3::MPEGaudioHeaderValid($MPEGaudioHeaderDecodeCache[$next4], false, false); } if ($MPEGaudioHeaderValidCache[$next4]) { - fseek($fd, -4, SEEK_CUR); + fseek($this->getid3->fp, -4, SEEK_CUR); - @$Distribution['bitrate'][$LongMPEGbitrateLookup[$head4]]++; - @$Distribution['layer'][$LongMPEGlayerLookup[$head4]]++; - @$Distribution['version'][$LongMPEGversionLookup[$head4]]++; - @$Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])]++; - @$Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]]++; + getid3_lib::safe_inc($Distribution['bitrate'][$LongMPEGbitrateLookup[$head4]]); + getid3_lib::safe_inc($Distribution['layer'][$LongMPEGlayerLookup[$head4]]); + getid3_lib::safe_inc($Distribution['version'][$LongMPEGversionLookup[$head4]]); + getid3_lib::safe_inc($Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])]); + getid3_lib::safe_inc($Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]]); if ($max_frames_scan && (++$frames_scanned >= $max_frames_scan)) { - $pct_data_scanned = (ftell($fd) - $ThisFileInfo['avdataoffset']) / ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']); - $ThisFileInfo['warning'][] = 'too many MPEG audio frames to scan, only scanned first '.$max_frames_scan.' frames ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.'; + $pct_data_scanned = (ftell($this->getid3->fp) - $info['avdataoffset']) / ($info['avdataend'] - $info['avdataoffset']); + $info['warning'][] = 'too many MPEG audio frames to scan, only scanned first '.$max_frames_scan.' frames ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.'; foreach ($Distribution as $key1 => $value1) { foreach ($value1 as $key2 => $value2) { $Distribution[$key1][$key2] = round($value2 / $pct_data_scanned); @@ -1281,7 +1290,7 @@ class getid3_mp3 } } unset($next4); - fseek($fd, $WhereWeWere - 3, SEEK_SET); + fseek($this->getid3->fp, $WhereWeWere - 3, SEEK_SET); } } @@ -1290,19 +1299,19 @@ class getid3_mp3 ksort($Distribution[$key], SORT_NUMERIC); } ksort($Distribution['version'], SORT_STRING); - $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = $Distribution['bitrate']; - $ThisFileInfo['mpeg']['audio']['frequency_distribution'] = $Distribution['frequency']; - $ThisFileInfo['mpeg']['audio']['layer_distribution'] = $Distribution['layer']; - $ThisFileInfo['mpeg']['audio']['version_distribution'] = $Distribution['version']; - $ThisFileInfo['mpeg']['audio']['padding_distribution'] = $Distribution['padding']; + $info['mpeg']['audio']['bitrate_distribution'] = $Distribution['bitrate']; + $info['mpeg']['audio']['frequency_distribution'] = $Distribution['frequency']; + $info['mpeg']['audio']['layer_distribution'] = $Distribution['layer']; + $info['mpeg']['audio']['version_distribution'] = $Distribution['version']; + $info['mpeg']['audio']['padding_distribution'] = $Distribution['padding']; if (count($Distribution['version']) > 1) { - $ThisFileInfo['error'][] = 'Corrupt file - more than one MPEG version detected'; + $info['error'][] = 'Corrupt file - more than one MPEG version detected'; } if (count($Distribution['layer']) > 1) { - $ThisFileInfo['error'][] = 'Corrupt file - more than one MPEG layer detected'; + $info['error'][] = 'Corrupt file - more than one MPEG layer detected'; } if (count($Distribution['frequency']) > 1) { - $ThisFileInfo['error'][] = 'Corrupt file - more than one MPEG sample rate detected'; + $info['error'][] = 'Corrupt file - more than one MPEG sample rate detected'; } @@ -1312,29 +1321,30 @@ class getid3_mp3 $bittotal += ($bitratevalue * $bitratecount); } } - $ThisFileInfo['mpeg']['audio']['frame_count'] = array_sum($Distribution['bitrate']); - if ($ThisFileInfo['mpeg']['audio']['frame_count'] == 0) { - $ThisFileInfo['error'][] = 'no MPEG audio frames found'; + $info['mpeg']['audio']['frame_count'] = array_sum($Distribution['bitrate']); + if ($info['mpeg']['audio']['frame_count'] == 0) { + $info['error'][] = 'no MPEG audio frames found'; return false; } - $ThisFileInfo['mpeg']['audio']['bitrate'] = ($bittotal / $ThisFileInfo['mpeg']['audio']['frame_count']); - $ThisFileInfo['mpeg']['audio']['bitrate_mode'] = ((count($Distribution['bitrate']) > 0) ? 'vbr' : 'cbr'); - $ThisFileInfo['mpeg']['audio']['sample_rate'] = getid3_lib::array_max($Distribution['frequency'], true); + $info['mpeg']['audio']['bitrate'] = ($bittotal / $info['mpeg']['audio']['frame_count']); + $info['mpeg']['audio']['bitrate_mode'] = ((count($Distribution['bitrate']) > 0) ? 'vbr' : 'cbr'); + $info['mpeg']['audio']['sample_rate'] = getid3_lib::array_max($Distribution['frequency'], true); - $ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['mpeg']['audio']['bitrate']; - $ThisFileInfo['audio']['bitrate_mode'] = $ThisFileInfo['mpeg']['audio']['bitrate_mode']; - $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['mpeg']['audio']['sample_rate']; - $ThisFileInfo['audio']['dataformat'] = 'mp'.getid3_lib::array_max($Distribution['layer'], true); - $ThisFileInfo['fileformat'] = $ThisFileInfo['audio']['dataformat']; + $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; + $info['audio']['bitrate_mode'] = $info['mpeg']['audio']['bitrate_mode']; + $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; + $info['audio']['dataformat'] = 'mp'.getid3_lib::array_max($Distribution['layer'], true); + $info['fileformat'] = $info['audio']['dataformat']; return true; } - function getOnlyMPEGaudioInfo($fd, &$ThisFileInfo, $avdataoffset, $BitrateHistogram=false) { - + function getOnlyMPEGaudioInfo($avdataoffset, $BitrateHistogram=false) { // looks for synch, decodes MPEG audio header + $info = &$this->getid3->info; + static $MPEGaudioVersionLookup; static $MPEGaudioLayerLookup; static $MPEGaudioBitrateLookup; @@ -1345,131 +1355,125 @@ class getid3_mp3 } - fseek($fd, $avdataoffset, SEEK_SET); - $sync_seek_buffer_size = min(128 * 1024, $ThisFileInfo['avdataend'] - $avdataoffset); + fseek($this->getid3->fp, $avdataoffset, SEEK_SET); + $sync_seek_buffer_size = min(128 * 1024, $info['avdataend'] - $avdataoffset); if ($sync_seek_buffer_size <= 0) { - $ThisFileInfo['error'][] = 'Invalid $sync_seek_buffer_size at offset '.$avdataoffset; + $info['error'][] = 'Invalid $sync_seek_buffer_size at offset '.$avdataoffset; return false; } - $header = fread($fd, $sync_seek_buffer_size); + $header = fread($this->getid3->fp, $sync_seek_buffer_size); $sync_seek_buffer_size = strlen($header); $SynchSeekOffset = 0; while ($SynchSeekOffset < $sync_seek_buffer_size) { - - if ((($avdataoffset + $SynchSeekOffset) < $ThisFileInfo['avdataend']) && !feof($fd)) { + if ((($avdataoffset + $SynchSeekOffset) < $info['avdataend']) && !feof($this->getid3->fp)) { if ($SynchSeekOffset > $sync_seek_buffer_size) { // if a synch's not found within the first 128k bytes, then give up - $ThisFileInfo['error'][] = 'Could not find valid MPEG audio synch within the first '.round($sync_seek_buffer_size / 1024).'kB'; - if (isset($ThisFileInfo['audio']['bitrate'])) { - unset($ThisFileInfo['audio']['bitrate']); + $info['error'][] = 'Could not find valid MPEG audio synch within the first '.round($sync_seek_buffer_size / 1024).'kB'; + if (isset($info['audio']['bitrate'])) { + unset($info['audio']['bitrate']); } - if (isset($ThisFileInfo['mpeg']['audio'])) { - unset($ThisFileInfo['mpeg']['audio']); + if (isset($info['mpeg']['audio'])) { + unset($info['mpeg']['audio']); } - if (empty($ThisFileInfo['mpeg'])) { - unset($ThisFileInfo['mpeg']); + if (empty($info['mpeg'])) { + unset($info['mpeg']); } return false; - } elseif (feof($fd)) { + } elseif (feof($this->getid3->fp)) { - $ThisFileInfo['error'][] = 'Could not find valid MPEG audio synch before end of file'; - if (isset($ThisFileInfo['audio']['bitrate'])) { - unset($ThisFileInfo['audio']['bitrate']); + $info['error'][] = 'Could not find valid MPEG audio synch before end of file'; + if (isset($info['audio']['bitrate'])) { + unset($info['audio']['bitrate']); } - if (isset($ThisFileInfo['mpeg']['audio'])) { - unset($ThisFileInfo['mpeg']['audio']); + if (isset($info['mpeg']['audio'])) { + unset($info['mpeg']['audio']); } - if (isset($ThisFileInfo['mpeg']) && (!is_array($ThisFileInfo['mpeg']) || (count($ThisFileInfo['mpeg']) == 0))) { - unset($ThisFileInfo['mpeg']); + if (isset($info['mpeg']) && (!is_array($info['mpeg']) || (count($info['mpeg']) == 0))) { + unset($info['mpeg']); } return false; } } if (($SynchSeekOffset + 1) >= strlen($header)) { - $ThisFileInfo['error'][] = 'Could not find valid MPEG synch before end of file'; + $info['error'][] = 'Could not find valid MPEG synch before end of file'; return false; } if (($header{$SynchSeekOffset} == "\xFF") && ($header{($SynchSeekOffset + 1)} > "\xE0")) { // synch detected - - if (!isset($FirstFrameThisfileInfo) && !isset($ThisFileInfo['mpeg']['audio'])) { - $FirstFrameThisfileInfo = $ThisFileInfo; + if (!isset($FirstFrameThisfileInfo) && !isset($info['mpeg']['audio'])) { + $FirstFrameThisfileInfo = $info; $FirstFrameAVDataOffset = $avdataoffset + $SynchSeekOffset; - if (!getid3_mp3::decodeMPEGaudioHeader($fd, $avdataoffset + $SynchSeekOffset, $FirstFrameThisfileInfo, false)) { + if (!$this->decodeMPEGaudioHeader($FirstFrameAVDataOffset, $FirstFrameThisfileInfo, false)) { // if this is the first valid MPEG-audio frame, save it in case it's a VBR header frame and there's // garbage between this frame and a valid sequence of MPEG-audio frames, to be restored below unset($FirstFrameThisfileInfo); } } - $dummy = $ThisFileInfo; // only overwrite real data if valid header found - if (getid3_mp3::decodeMPEGaudioHeader($fd, $avdataoffset + $SynchSeekOffset, $dummy, true)) { - $ThisFileInfo = $dummy; - $ThisFileInfo['avdataoffset'] = $avdataoffset + $SynchSeekOffset; - switch (@$ThisFileInfo['fileformat']) { + $dummy = $info; // only overwrite real data if valid header found + if ($this->decodeMPEGaudioHeader($avdataoffset + $SynchSeekOffset, $dummy, true)) { + $info = $dummy; + $info['avdataoffset'] = $avdataoffset + $SynchSeekOffset; + switch (isset($info['fileformat']) ? $info['fileformat'] : '') { case '': case 'id3': case 'ape': case 'mp3': - $ThisFileInfo['fileformat'] = 'mp3'; - $ThisFileInfo['audio']['dataformat'] = 'mp3'; + $info['fileformat'] = 'mp3'; + $info['audio']['dataformat'] = 'mp3'; break; } if (isset($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode']) && ($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr')) { - if (!(abs($ThisFileInfo['audio']['bitrate'] - $FirstFrameThisfileInfo['audio']['bitrate']) <= 1)) { + if (!(abs($info['audio']['bitrate'] - $FirstFrameThisfileInfo['audio']['bitrate']) <= 1)) { // If there is garbage data between a valid VBR header frame and a sequence // of valid MPEG-audio frames the VBR data is no longer discarded. - $ThisFileInfo = $FirstFrameThisfileInfo; - $ThisFileInfo['avdataoffset'] = $FirstFrameAVDataOffset; - $ThisFileInfo['fileformat'] = 'mp3'; - $ThisFileInfo['audio']['dataformat'] = 'mp3'; - $dummy = $ThisFileInfo; + $info = $FirstFrameThisfileInfo; + $info['avdataoffset'] = $FirstFrameAVDataOffset; + $info['fileformat'] = 'mp3'; + $info['audio']['dataformat'] = 'mp3'; + $dummy = $info; unset($dummy['mpeg']['audio']); $GarbageOffsetStart = $FirstFrameAVDataOffset + $FirstFrameThisfileInfo['mpeg']['audio']['framelength']; $GarbageOffsetEnd = $avdataoffset + $SynchSeekOffset; - if (getid3_mp3::decodeMPEGaudioHeader($fd, $GarbageOffsetEnd, $dummy, true, true)) { - - $ThisFileInfo = $dummy; - $ThisFileInfo['avdataoffset'] = $GarbageOffsetEnd; - $ThisFileInfo['warning'][] = 'apparently-valid VBR header not used because could not find '.GETID3_MP3_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.'), but did find valid CBR stream starting at '.$GarbageOffsetEnd; - + if ($this->decodeMPEGaudioHeader($GarbageOffsetEnd, $dummy, true, true)) { + $info = $dummy; + $info['avdataoffset'] = $GarbageOffsetEnd; + $info['warning'][] = 'apparently-valid VBR header not used because could not find '.GETID3_MP3_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.'), but did find valid CBR stream starting at '.$GarbageOffsetEnd; } else { - - $ThisFileInfo['warning'][] = 'using data from VBR header even though could not find '.GETID3_MP3_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.')'; - + $info['warning'][] = 'using data from VBR header even though could not find '.GETID3_MP3_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.')'; } } } - if (isset($ThisFileInfo['mpeg']['audio']['bitrate_mode']) && ($ThisFileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr') && !isset($ThisFileInfo['mpeg']['audio']['VBR_method'])) { + if (isset($info['mpeg']['audio']['bitrate_mode']) && ($info['mpeg']['audio']['bitrate_mode'] == 'vbr') && !isset($info['mpeg']['audio']['VBR_method'])) { // VBR file with no VBR header $BitrateHistogram = true; } if ($BitrateHistogram) { - $ThisFileInfo['mpeg']['audio']['stereo_distribution'] = array('stereo'=>0, 'joint stereo'=>0, 'dual channel'=>0, 'mono'=>0); - $ThisFileInfo['mpeg']['audio']['version_distribution'] = array('1'=>0, '2'=>0, '2.5'=>0); + $info['mpeg']['audio']['stereo_distribution'] = array('stereo'=>0, 'joint stereo'=>0, 'dual channel'=>0, 'mono'=>0); + $info['mpeg']['audio']['version_distribution'] = array('1'=>0, '2'=>0, '2.5'=>0); - if ($ThisFileInfo['mpeg']['audio']['version'] == '1') { - if ($ThisFileInfo['mpeg']['audio']['layer'] == 3) { - $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 40000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 320000=>0); - } elseif ($ThisFileInfo['mpeg']['audio']['layer'] == 2) { - $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 320000=>0, 384000=>0); - } elseif ($ThisFileInfo['mpeg']['audio']['layer'] == 1) { - $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 64000=>0, 96000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 288000=>0, 320000=>0, 352000=>0, 384000=>0, 416000=>0, 448000=>0); + if ($info['mpeg']['audio']['version'] == '1') { + if ($info['mpeg']['audio']['layer'] == 3) { + $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 40000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 320000=>0); + } elseif ($info['mpeg']['audio']['layer'] == 2) { + $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 320000=>0, 384000=>0); + } elseif ($info['mpeg']['audio']['layer'] == 1) { + $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 64000=>0, 96000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 288000=>0, 320000=>0, 352000=>0, 384000=>0, 416000=>0, 448000=>0); } - } elseif ($ThisFileInfo['mpeg']['audio']['layer'] == 1) { - $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 144000=>0, 160000=>0, 176000=>0, 192000=>0, 224000=>0, 256000=>0); + } elseif ($info['mpeg']['audio']['layer'] == 1) { + $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 144000=>0, 160000=>0, 176000=>0, 192000=>0, 224000=>0, 256000=>0); } else { - $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 8000=>0, 16000=>0, 24000=>0, 32000=>0, 40000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 144000=>0, 160000=>0); + $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 8000=>0, 16000=>0, 24000=>0, 32000=>0, 40000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 144000=>0, 160000=>0); } - $dummy = array('error'=>$ThisFileInfo['error'], 'warning'=>$ThisFileInfo['warning'], 'avdataend'=>$ThisFileInfo['avdataend'], 'avdataoffset'=>$ThisFileInfo['avdataoffset']); - $synchstartoffset = $ThisFileInfo['avdataoffset']; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $dummy = array('error'=>$info['error'], 'warning'=>$info['warning'], 'avdataend'=>$info['avdataend'], 'avdataoffset'=>$info['avdataoffset']); + $synchstartoffset = $info['avdataoffset']; + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); // you can play with these numbers: $max_frames_scan = 50000; @@ -1483,116 +1487,102 @@ class getid3_mp3 $frames_scan_per_segment = ceil($max_frames_scan / $max_scan_segments); $pct_data_scanned = 0; for ($current_segment = 0; $current_segment < $max_scan_segments; $current_segment++) { -//echo 'was at '.ftell($fd).'
'; $frames_scanned_this_segment = 0; - if (ftell($fd) >= $ThisFileInfo['avdataend']) { -//echo 'breaking because current position ('.ftell($fd).') is >= $ThisFileInfo[avdataend] ('.$ThisFileInfo['avdataend'].')
'; + if (ftell($this->getid3->fp) >= $info['avdataend']) { break; } - $scan_start_offset[$current_segment] = max(ftell($fd), $ThisFileInfo['avdataoffset'] + round($current_segment * (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) / $max_scan_segments))); -//echo 'start at '.$scan_start_offset[$current_segment].'
'; + $scan_start_offset[$current_segment] = max(ftell($this->getid3->fp), $info['avdataoffset'] + round($current_segment * (($info['avdataend'] - $info['avdataoffset']) / $max_scan_segments))); if ($current_segment > 0) { - fseek($fd, $scan_start_offset[$current_segment], SEEK_SET); - $buffer_4k = fread($fd, 4096); + fseek($this->getid3->fp, $scan_start_offset[$current_segment], SEEK_SET); + $buffer_4k = fread($this->getid3->fp, 4096); for ($j = 0; $j < (strlen($buffer_4k) - 4); $j++) { if (($buffer_4k{$j} == "\xFF") && ($buffer_4k{($j + 1)} > "\xE0")) { // synch detected - if (getid3_mp3::decodeMPEGaudioHeader($fd, $scan_start_offset[$current_segment] + $j, $dummy, false, false, $FastMode)) { + if ($this->decodeMPEGaudioHeader($scan_start_offset[$current_segment] + $j, $dummy, false, false, $FastMode)) { $calculated_next_offset = $scan_start_offset[$current_segment] + $j + $dummy['mpeg']['audio']['framelength']; - if (getid3_mp3::decodeMPEGaudioHeader($fd, $calculated_next_offset, $dummy, false, false, $FastMode)) { + if ($this->decodeMPEGaudioHeader($calculated_next_offset, $dummy, false, false, $FastMode)) { $scan_start_offset[$current_segment] += $j; break; - } else { -//echo 'header['.__LINE__.'] at '.($calculated_next_offset).' invalid
'; } - } else { -//echo 'header['.__LINE__.'] at '.($scan_start_offset[$current_segment] + $j).' invalid
'; } } } } -//echo 'actually start at '.$scan_start_offset[$current_segment].'
'; $synchstartoffset = $scan_start_offset[$current_segment]; - while (getid3_mp3::decodeMPEGaudioHeader($fd, $synchstartoffset, $dummy, false, false, $FastMode)) { + while ($this->decodeMPEGaudioHeader($synchstartoffset, $dummy, false, false, $FastMode)) { $FastMode = true; $thisframebitrate = $MPEGaudioBitrateLookup[$MPEGaudioVersionLookup[$dummy['mpeg']['audio']['raw']['version']]][$MPEGaudioLayerLookup[$dummy['mpeg']['audio']['raw']['layer']]][$dummy['mpeg']['audio']['raw']['bitrate']]; if (empty($dummy['mpeg']['audio']['framelength'])) { $SynchErrorsFound++; $synchstartoffset++; -//echo ' [Ø] '; } else { -//echo ' . '; - @$ThisFileInfo['mpeg']['audio']['bitrate_distribution'][$thisframebitrate]++; - @$ThisFileInfo['mpeg']['audio']['stereo_distribution'][$dummy['mpeg']['audio']['channelmode']]++; - @$ThisFileInfo['mpeg']['audio']['version_distribution'][$dummy['mpeg']['audio']['version']]++; - + getid3_lib::safe_inc($info['mpeg']['audio']['bitrate_distribution'][$thisframebitrate]); + getid3_lib::safe_inc($info['mpeg']['audio']['stereo_distribution'][$dummy['mpeg']['audio']['channelmode']]); + getid3_lib::safe_inc($info['mpeg']['audio']['version_distribution'][$dummy['mpeg']['audio']['version']]); $synchstartoffset += $dummy['mpeg']['audio']['framelength']; } $frames_scanned++; if ($frames_scan_per_segment && (++$frames_scanned_this_segment >= $frames_scan_per_segment)) { - $this_pct_scanned = (ftell($fd) - $scan_start_offset[$current_segment]) / ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']); + $this_pct_scanned = (ftell($this->getid3->fp) - $scan_start_offset[$current_segment]) / ($info['avdataend'] - $info['avdataoffset']); if (($current_segment == 0) && (($this_pct_scanned * $max_scan_segments) >= 1)) { // file likely contains < $max_frames_scan, just scan as one segment $max_scan_segments = 1; $frames_scan_per_segment = $max_frames_scan; } else { $pct_data_scanned += $this_pct_scanned; -//var_dump($pct_data_scanned); -//exit; break; } } } -//echo '
'; } if ($pct_data_scanned > 0) { - $ThisFileInfo['warning'][] = 'too many MPEG audio frames to scan, only scanned '.$frames_scanned.' frames in '.$max_scan_segments.' segments ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.'; - foreach ($ThisFileInfo['mpeg']['audio'] as $key1 => $value1) { - if (!eregi('_distribution$', $key1)) { + $info['warning'][] = 'too many MPEG audio frames to scan, only scanned '.$frames_scanned.' frames in '.$max_scan_segments.' segments ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.'; + foreach ($info['mpeg']['audio'] as $key1 => $value1) { + if (!preg_match('#_distribution$#i', $key1)) { continue; } foreach ($value1 as $key2 => $value2) { - $ThisFileInfo['mpeg']['audio'][$key1][$key2] = round($value2 / $pct_data_scanned); + $info['mpeg']['audio'][$key1][$key2] = round($value2 / $pct_data_scanned); } } } if ($SynchErrorsFound > 0) { - $ThisFileInfo['warning'][] = 'Found '.$SynchErrorsFound.' synch errors in histogram analysis'; + $info['warning'][] = 'Found '.$SynchErrorsFound.' synch errors in histogram analysis'; //return false; } $bittotal = 0; $framecounter = 0; - foreach ($ThisFileInfo['mpeg']['audio']['bitrate_distribution'] as $bitratevalue => $bitratecount) { + foreach ($info['mpeg']['audio']['bitrate_distribution'] as $bitratevalue => $bitratecount) { $framecounter += $bitratecount; if ($bitratevalue != 'free') { $bittotal += ($bitratevalue * $bitratecount); } } if ($framecounter == 0) { - $ThisFileInfo['error'][] = 'Corrupt MP3 file: framecounter == zero'; + $info['error'][] = 'Corrupt MP3 file: framecounter == zero'; return false; } - $ThisFileInfo['mpeg']['audio']['frame_count'] = getid3_lib::CastAsInt($framecounter); - $ThisFileInfo['mpeg']['audio']['bitrate'] = ($bittotal / $framecounter); + $info['mpeg']['audio']['frame_count'] = getid3_lib::CastAsInt($framecounter); + $info['mpeg']['audio']['bitrate'] = ($bittotal / $framecounter); - $ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['mpeg']['audio']['bitrate']; + $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; // Definitively set VBR vs CBR, even if the Xing/LAME/VBRI header says differently $distinct_bitrates = 0; - foreach ($ThisFileInfo['mpeg']['audio']['bitrate_distribution'] as $bitrate_value => $bitrate_count) { + foreach ($info['mpeg']['audio']['bitrate_distribution'] as $bitrate_value => $bitrate_count) { if ($bitrate_count > 0) { $distinct_bitrates++; } } if ($distinct_bitrates > 1) { - $ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'vbr'; + $info['mpeg']['audio']['bitrate_mode'] = 'vbr'; } else { - $ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'cbr'; + $info['mpeg']['audio']['bitrate_mode'] = 'cbr'; } - $ThisFileInfo['audio']['bitrate_mode'] = $ThisFileInfo['mpeg']['audio']['bitrate_mode']; + $info['audio']['bitrate_mode'] = $info['mpeg']['audio']['bitrate_mode']; } @@ -1601,20 +1591,20 @@ class getid3_mp3 } $SynchSeekOffset++; - if (($avdataoffset + $SynchSeekOffset) >= $ThisFileInfo['avdataend']) { + if (($avdataoffset + $SynchSeekOffset) >= $info['avdataend']) { // end of file/data - if (empty($ThisFileInfo['mpeg']['audio'])) { + if (empty($info['mpeg']['audio'])) { - $ThisFileInfo['error'][] = 'could not find valid MPEG synch before end of file'; - if (isset($ThisFileInfo['audio']['bitrate'])) { - unset($ThisFileInfo['audio']['bitrate']); + $info['error'][] = 'could not find valid MPEG synch before end of file'; + if (isset($info['audio']['bitrate'])) { + unset($info['audio']['bitrate']); } - if (isset($ThisFileInfo['mpeg']['audio'])) { - unset($ThisFileInfo['mpeg']['audio']); + if (isset($info['mpeg']['audio'])) { + unset($info['mpeg']['audio']); } - if (isset($ThisFileInfo['mpeg']) && (!is_array($ThisFileInfo['mpeg']) || empty($ThisFileInfo['mpeg']))) { - unset($ThisFileInfo['mpeg']); + if (isset($info['mpeg']) && (!is_array($info['mpeg']) || empty($info['mpeg']))) { + unset($info['mpeg']); } return false; @@ -1623,24 +1613,24 @@ class getid3_mp3 } } - $ThisFileInfo['audio']['channels'] = $ThisFileInfo['mpeg']['audio']['channels']; - $ThisFileInfo['audio']['channelmode'] = $ThisFileInfo['mpeg']['audio']['channelmode']; - $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['mpeg']['audio']['sample_rate']; + $info['audio']['channels'] = $info['mpeg']['audio']['channels']; + $info['audio']['channelmode'] = $info['mpeg']['audio']['channelmode']; + $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; return true; } - function MPEGaudioVersionArray() { + static function MPEGaudioVersionArray() { static $MPEGaudioVersion = array('2.5', false, '2', '1'); return $MPEGaudioVersion; } - function MPEGaudioLayerArray() { + static function MPEGaudioLayerArray() { static $MPEGaudioLayer = array(false, 3, 2, 1); return $MPEGaudioLayer; } - function MPEGaudioBitrateArray() { + static function MPEGaudioBitrateArray() { static $MPEGaudioBitrate; if (empty($MPEGaudioBitrate)) { $MPEGaudioBitrate = array ( @@ -1659,7 +1649,7 @@ class getid3_mp3 return $MPEGaudioBitrate; } - function MPEGaudioFrequencyArray() { + static function MPEGaudioFrequencyArray() { static $MPEGaudioFrequency; if (empty($MPEGaudioFrequency)) { $MPEGaudioFrequency = array ( @@ -1671,12 +1661,12 @@ class getid3_mp3 return $MPEGaudioFrequency; } - function MPEGaudioChannelModeArray() { + static function MPEGaudioChannelModeArray() { static $MPEGaudioChannelMode = array('stereo', 'joint stereo', 'dual channel', 'mono'); return $MPEGaudioChannelMode; } - function MPEGaudioModeExtensionArray() { + static function MPEGaudioModeExtensionArray() { static $MPEGaudioModeExtension; if (empty($MPEGaudioModeExtension)) { $MPEGaudioModeExtension = array ( @@ -1688,16 +1678,16 @@ class getid3_mp3 return $MPEGaudioModeExtension; } - function MPEGaudioEmphasisArray() { + static function MPEGaudioEmphasisArray() { static $MPEGaudioEmphasis = array('none', '50/15ms', false, 'CCIT J.17'); return $MPEGaudioEmphasis; } - function MPEGaudioHeaderBytesValid($head4, $allowBitrate15=false) { + static function MPEGaudioHeaderBytesValid($head4, $allowBitrate15=false) { return getid3_mp3::MPEGaudioHeaderValid(getid3_mp3::MPEGaudioHeaderDecode($head4), false, $allowBitrate15); } - function MPEGaudioHeaderValid($rawarray, $echoerrors=false, $allowBitrate15=false) { + static function MPEGaudioHeaderValid($rawarray, $echoerrors=false, $allowBitrate15=false) { if (($rawarray['synch'] & 0x0FFE) != 0x0FFE) { return false; } @@ -1769,7 +1759,7 @@ class getid3_mp3 return true; } - function MPEGaudioHeaderDecode($Header4Bytes) { + static function MPEGaudioHeaderDecode($Header4Bytes) { // AAAA AAAA AAAB BCCD EEEE FFGH IIJJ KLMM // A - Frame sync (all bits set) // B - MPEG Audio version ID @@ -1806,7 +1796,7 @@ class getid3_mp3 return $MPEGrawHeader; } - function MPEGaudioFrameLength(&$bitrate, &$version, &$layer, $padding, &$samplerate) { + static function MPEGaudioFrameLength(&$bitrate, &$version, &$layer, $padding, &$samplerate) { static $AudioFrameLengthCache = array(); if (!isset($AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate])) { @@ -1867,28 +1857,27 @@ class getid3_mp3 return $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate]; } - function ClosestStandardMP3Bitrate($bitrate) { - static $StandardBitrates = array(320000, 256000, 224000, 192000, 160000, 128000, 112000, 96000, 80000, 64000, 56000, 48000, 40000, 32000, 24000, 16000, 8000); - static $BitrateTable = array(0=>'-'); - $roundbitrate = intval(round($bitrate, -3)); - if (!isset($BitrateTable[$roundbitrate])) { - if ($roundbitrate > 320000) { - $BitrateTable[$roundbitrate] = round($bitrate, -4); + static function ClosestStandardMP3Bitrate($bit_rate) { + static $standard_bit_rates = array (320000, 256000, 224000, 192000, 160000, 128000, 112000, 96000, 80000, 64000, 56000, 48000, 40000, 32000, 24000, 16000, 8000); + static $bit_rate_table = array (0=>'-'); + $round_bit_rate = intval(round($bit_rate, -3)); + if (!isset($bit_rate_table[$round_bit_rate])) { + if ($round_bit_rate > max($standard_bit_rates)) { + $bit_rate_table[$round_bit_rate] = round($bit_rate, 2 - strlen($bit_rate)); } else { - $LastBitrate = 320000; - foreach ($StandardBitrates as $StandardBitrate) { - $BitrateTable[$roundbitrate] = $StandardBitrate; - if ($roundbitrate >= $StandardBitrate - (($LastBitrate - $StandardBitrate) / 2)) { + $bit_rate_table[$round_bit_rate] = max($standard_bit_rates); + foreach ($standard_bit_rates as $standard_bit_rate) { + if ($round_bit_rate >= $standard_bit_rate + (($bit_rate_table[$round_bit_rate] - $standard_bit_rate) / 2)) { break; } - $LastBitrate = $StandardBitrate; + $bit_rate_table[$round_bit_rate] = $standard_bit_rate; } } } - return $BitrateTable[$roundbitrate]; + return $bit_rate_table[$round_bit_rate]; } - function XingVBRidOffset($version, $channelmode) { + static function XingVBRidOffset($version, $channelmode) { static $XingVBRidOffsetCache = array(); if (empty($XingVBRidOffset)) { $XingVBRidOffset = array ( @@ -1914,7 +1903,7 @@ class getid3_mp3 return $XingVBRidOffset[$version][$channelmode]; } - function LAMEvbrMethodLookup($VBRmethodID) { + static function LAMEvbrMethodLookup($VBRmethodID) { static $LAMEvbrMethodLookup = array( 0x00 => 'unknown', 0x01 => 'cbr', @@ -1930,7 +1919,7 @@ class getid3_mp3 return (isset($LAMEvbrMethodLookup[$VBRmethodID]) ? $LAMEvbrMethodLookup[$VBRmethodID] : ''); } - function LAMEmiscStereoModeLookup($StereoModeID) { + static function LAMEmiscStereoModeLookup($StereoModeID) { static $LAMEmiscStereoModeLookup = array( 0 => 'mono', 1 => 'stereo', @@ -1944,7 +1933,7 @@ class getid3_mp3 return (isset($LAMEmiscStereoModeLookup[$StereoModeID]) ? $LAMEmiscStereoModeLookup[$StereoModeID] : ''); } - function LAMEmiscSourceSampleFrequencyLookup($SourceSampleFrequencyID) { + static function LAMEmiscSourceSampleFrequencyLookup($SourceSampleFrequencyID) { static $LAMEmiscSourceSampleFrequencyLookup = array( 0 => '<= 32 kHz', 1 => '44.1 kHz', @@ -1954,7 +1943,7 @@ class getid3_mp3 return (isset($LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID]) ? $LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID] : ''); } - function LAMEsurroundInfoLookup($SurroundInfoID) { + static function LAMEsurroundInfoLookup($SurroundInfoID) { static $LAMEsurroundInfoLookup = array( 0 => 'no surround info', 1 => 'DPL encoding', @@ -1964,7 +1953,7 @@ class getid3_mp3 return (isset($LAMEsurroundInfoLookup[$SurroundInfoID]) ? $LAMEsurroundInfoLookup[$SurroundInfoID] : 'reserved'); } - function LAMEpresetUsedLookup($LAMEtag) { + static function LAMEpresetUsedLookup($LAMEtag) { if ($LAMEtag['preset_used_id'] == 0) { // no preset used (LAME >=3.93) diff --git a/apps/media/getID3/getid3/module.audio.mpc.php b/3rdparty/getid3/module.audio.mpc.php similarity index 72% rename from apps/media/getID3/getid3/module.audio.mpc.php rename to 3rdparty/getid3/module.audio.mpc.php index 66200ad02a..9a0b16d9ab 100644 --- a/apps/media/getID3/getid3/module.audio.mpc.php +++ b/3rdparty/getid3/module.audio.mpc.php @@ -14,42 +14,44 @@ ///////////////////////////////////////////////////////////////// -class getid3_mpc +class getid3_mpc extends getid3_handler { - function getid3_mpc(&$fd, &$ThisFileInfo) { - $ThisFileInfo['mpc']['header'] = array(); - $thisfile_mpc_header = &$ThisFileInfo['mpc']['header']; + function Analyze() { + $info = &$this->getid3->info; - $ThisFileInfo['fileformat'] = 'mpc'; - $ThisFileInfo['audio']['dataformat'] = 'mpc'; - $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; - $ThisFileInfo['audio']['channels'] = 2; // up to SV7 the format appears to have been hardcoded for stereo only - $ThisFileInfo['audio']['lossless'] = false; + $info['mpc']['header'] = array(); + $thisfile_mpc_header = &$info['mpc']['header']; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $MPCheaderData = fread($fd, 4); - $ThisFileInfo['mpc']['header']['preamble'] = substr($MPCheaderData, 0, 4); // should be 'MPCK' (SV8) or 'MP+' (SV7), otherwise possible stream data (SV4-SV6) - if (ereg('^MPCK', $ThisFileInfo['mpc']['header']['preamble'])) { + $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($fd, $ThisFileInfo); + return $this->ParseMPCsv8(); - } elseif (ereg('^MP\+', $ThisFileInfo['mpc']['header']['preamble'])) { + } elseif (preg_match('#^MP\+#', $info['mpc']['header']['preamble'])) { // this is SV7 - return $this->ParseMPCsv7($fd, $ThisFileInfo); + 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($fd, $ThisFileInfo); + return $this->ParseMPCsv6(); } else { - $ThisFileInfo['error'][] = 'Expecting "MP+" or "MPCK" at offset '.$ThisFileInfo['avdataoffset'].', found "'.substr($MPCheaderData, 0, 4).'"'; - unset($ThisFileInfo['fileformat']); - unset($ThisFileInfo['mpc']); + $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; } @@ -57,35 +59,36 @@ class getid3_mpc } - function ParseMPCsv8(&$fd, &$ThisFileInfo) { + function ParseMPCsv8() { // this is SV8 // http://trac.musepack.net/trac/wiki/SV8Specification - $thisfile_mpc_header = &$ThisFileInfo['mpc']['header']; + $info = &$this->getid3->info; + $thisfile_mpc_header = &$info['mpc']['header']; $keyNameSize = 2; $maxHandledPacketLength = 9; // specs say: "n*8; 0 < n < 10" - $offset = ftell($fd); - while ($offset < $ThisFileInfo['avdataend']) { + $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($fd, $keyNameSize + $maxHandledPacketLength); + $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']) { - $ThisFileInfo['error'][] = 'Found unexpected key value "'.$thisPacket['key'].'" at offset '.$thisPacket['offset']; + $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) { - $ThisFileInfo['error'][] = 'Did not find expected packet length within '.$maxHandledPacketLength.' bytes at offset '.($thisPacket['offset'] + $keyNameSize); + $info['error'][] = 'Did not find expected packet length within '.$maxHandledPacketLength.' bytes at offset '.($thisPacket['offset'] + $keyNameSize); return false; } $packet_offset += $packetLength; @@ -95,7 +98,7 @@ class getid3_mpc case 'SH': // Stream Header $moreBytesToRead = $thisPacket['packet_size'] - $keyNameSize - $maxHandledPacketLength; if ($moreBytesToRead > 0) { - $MPCheaderData .= fread($fd, $moreBytesToRead); + $MPCheaderData .= fread($this->getid3->fp, $moreBytesToRead); } $thisPacket['crc'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 4)); $packet_offset += 4; @@ -124,16 +127,16 @@ class getid3_mpc $thisfile_mpc_header['samples'] = $thisPacket['sample_count']; $thisfile_mpc_header['stream_version_major'] = $thisPacket['stream_version']; - $ThisFileInfo['audio']['channels'] = $thisPacket['channels']; - $ThisFileInfo['audio']['sample_rate'] = $thisPacket['sample_frequency']; - $ThisFileInfo['playtime_seconds'] = $thisPacket['sample_count'] / $thisPacket['sample_frequency']; - $ThisFileInfo['audio']['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; + $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($fd, $moreBytesToRead); + $MPCheaderData .= fread($this->getid3->fp, $moreBytesToRead); } $thisPacket['replaygain_version'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1)); $packet_offset += 1; @@ -146,16 +149,16 @@ class getid3_mpc $thisPacket['replaygain_album_peak'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2)); $packet_offset += 2; - if ($thisPacket['replaygain_title_gain']) { $ThisFileInfo['replay_gain']['title']['gain'] = $thisPacket['replaygain_title_gain']; } - if ($thisPacket['replaygain_title_peak']) { $ThisFileInfo['replay_gain']['title']['peak'] = $thisPacket['replaygain_title_peak']; } - if ($thisPacket['replaygain_album_gain']) { $ThisFileInfo['replay_gain']['album']['gain'] = $thisPacket['replaygain_album_gain']; } - if ($thisPacket['replaygain_album_peak']) { $ThisFileInfo['replay_gain']['album']['peak'] = $thisPacket['replaygain_album_peak']; } + 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($fd, $moreBytesToRead); + $MPCheaderData .= fread($this->getid3->fp, $moreBytesToRead); } $profile_pns = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1)); $packet_offset += 1; @@ -171,8 +174,8 @@ class getid3_mpc $packet_offset += 1; $thisPacket['version'] = $thisPacket['version_major'].'.'.$thisPacket['version_minor'].'.'.$thisPacket['version_build']; - $ThisFileInfo['audio']['encoder'] = 'MPC v'.$thisPacket['version'].' ('.(($thisPacket['version_minor'] % 2) ? 'unstable' : 'stable').')'; - $thisfile_mpc_header['encoder_version'] = $ThisFileInfo['audio']['encoder']; + $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; @@ -191,28 +194,30 @@ class getid3_mpc break; default: - $ThisFileInfo['error'][] = 'Found unhandled key type "'.$thisPacket['key'].'" at offset '.$thisPacket['offset']; + $info['error'][] = 'Found unhandled key type "'.$thisPacket['key'].'" at offset '.$thisPacket['offset']; return false; break; } if (!empty($thisPacket)) { - $ThisFileInfo['mpc']['packets'][] = $thisPacket; + $info['mpc']['packets'][] = $thisPacket; } - fseek($fd, $offset); + fseek($this->getid3->fp, $offset); } $thisfile_mpc_header['size'] = $offset; return true; } - function ParseMPCsv7(&$fd, &$ThisFileInfo) { + function ParseMPCsv7() { // this is SV7 // http://www.uni-jena.de/~pfk/mpp/sv8/header.html - $thisfile_mpc_header = &$ThisFileInfo['mpc']['header']; + + $info = &$this->getid3->info; + $thisfile_mpc_header = &$info['mpc']['header']; $offset = 0; $thisfile_mpc_header['size'] = 28; - $MPCheaderData = $ThisFileInfo['mpc']['header']['preamble']; - $MPCheaderData .= fread($fd, $thisfile_mpc_header['size'] - strlen($ThisFileInfo['mpc']['header']['preamble'])); + $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)); @@ -223,7 +228,7 @@ class getid3_mpc $offset += 4; if ($thisfile_mpc_header['stream_version_major'] != 7) { - $ThisFileInfo['error'][] = 'Only Musepack SV7 supported (this file claims to be v'.$thisfile_mpc_header['stream_version_major'].')'; + $info['error'][] = 'Only Musepack SV7 supported (this file claims to be v'.$thisfile_mpc_header['stream_version_major'].')'; return false; } @@ -262,22 +267,22 @@ class getid3_mpc $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) { - $ThisFileInfo['error'][] = 'Corrupt MPC file: frequency == zero'; + $info['error'][] = 'Corrupt MPC file: frequency == zero'; return false; } - $ThisFileInfo['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']) * $ThisFileInfo['audio']['channels']; + $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']; - $ThisFileInfo['playtime_seconds'] = ($thisfile_mpc_header['samples'] / $ThisFileInfo['audio']['channels']) / $ThisFileInfo['audio']['sample_rate']; - if ($ThisFileInfo['playtime_seconds'] == 0) { - $ThisFileInfo['error'][] = 'Corrupt MPC file: playtime_seconds == zero'; + $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 - $ThisFileInfo['avdataoffset'] += $thisfile_mpc_header['size']; + $info['avdataoffset'] += $thisfile_mpc_header['size']; - $ThisFileInfo['audio']['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; + $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']); @@ -296,37 +301,39 @@ class getid3_mpc } $thisfile_mpc_header['encoder_version'] = $this->MPCencoderVersionLookup($thisfile_mpc_header['raw']['encoder_version']); - $ThisFileInfo['replay_gain']['track']['adjustment'] = $thisfile_mpc_header['title_gain_db']; - $ThisFileInfo['replay_gain']['album']['adjustment'] = $thisfile_mpc_header['album_gain_db']; + $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) { - $ThisFileInfo['replay_gain']['track']['peak'] = $thisfile_mpc_header['title_peak']; + $info['replay_gain']['track']['peak'] = $thisfile_mpc_header['title_peak']; } elseif (round($thisfile_mpc_header['max_level'] * 1.18) > 0) { - $ThisFileInfo['replay_gain']['track']['peak'] = getid3_lib::CastAsInt(round($thisfile_mpc_header['max_level'] * 1.18)); // why? I don't know - see mppdec.c + $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) { - $ThisFileInfo['replay_gain']['album']['peak'] = $thisfile_mpc_header['album_peak']; + $info['replay_gain']['album']['peak'] = $thisfile_mpc_header['album_peak']; } - //$ThisFileInfo['audio']['encoder'] = 'SV'.$thisfile_mpc_header['stream_version_major'].'.'.$thisfile_mpc_header['stream_version_minor'].', '.$thisfile_mpc_header['encoder_version']; - $ThisFileInfo['audio']['encoder'] = $thisfile_mpc_header['encoder_version']; - $ThisFileInfo['audio']['encoder_options'] = $thisfile_mpc_header['profile']; + //$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; } - function ParseMPCsv6(&$fd, &$ThisFileInfo) { + function ParseMPCsv6() { // this is SV4 - SV6 - $thisfile_mpc_header = &$ThisFileInfo['mpc']['header']; + + $info = &$this->getid3->info; + $thisfile_mpc_header = &$info['mpc']['header']; $offset = 0; - $thisfile_mpc_header['size'] = 8; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $MPCheaderData = fread($fd, $thisfile_mpc_header['size']); + $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 - $ThisFileInfo['avdataoffset'] += $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)); @@ -362,29 +369,29 @@ class getid3_mpc break; default: - $ThisFileInfo['error'] = 'Expecting 4, 5 or 6 in version field, found '.$thisfile_mpc_header['stream_version_major'].' instead'; - unset($ThisFileInfo['mpc']); + $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)) { - $ThisFileInfo['warning'][] = 'Block size expected to be 1, actual value found: '.$thisfile_mpc_header['block_size']; + $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 - $ThisFileInfo['audio']['sample_rate'] = $thisfile_mpc_header['sample_rate']; - $thisfile_mpc_header['samples'] = $thisfile_mpc_header['frame_count'] * 1152 * $ThisFileInfo['audio']['channels']; + $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) { - $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; + $info['audio']['bitrate_mode'] = 'vbr'; } else { - $ThisFileInfo['audio']['bitrate_mode'] = 'cbr'; + $info['audio']['bitrate_mode'] = 'cbr'; } - $ThisFileInfo['mpc']['bitrate'] = ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8 * 44100 / $thisfile_mpc_header['frame_count'] / 1152; - $ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['mpc']['bitrate']; - $ThisFileInfo['audio']['encoder'] = 'SV'.$thisfile_mpc_header['stream_version_major']; + $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; } diff --git a/3rdparty/getid3/module.audio.ogg.php b/3rdparty/getid3/module.audio.ogg.php new file mode 100644 index 0000000000..c987fa67d7 --- /dev/null +++ b/3rdparty/getid3/module.audio.ogg.php @@ -0,0 +1,705 @@ + // +// 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 +{ + var $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 + + 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 + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + + $oggpageinfo = $this->ParseOggPageHeader(); + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; + + if (ftell($this->getid3->fp) >= $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 = fread($this->getid3->fp, $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 = fread($this->getid3->fp, $oggpageinfo['page_length']); + fseek($this->getid3->fp, $oggpageinfo['page_end_offset'], SEEK_SET); + + 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")); + fseek($this->getid3->fp, $oggpageinfo['page_start_offset'], SEEK_SET); + + +$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 = fread($this->getid3->fp, $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->ParseVorbisCommentsFilepointer(); + break; + + case 'flac': + $getid3_flac = new getid3_flac($this->getid3); + if (!$getid3_flac->FLACparseMETAdata()) { + $info['error'][] = 'Failed to parse FLAC headers'; + return false; + } + unset($getid3_flac); + break; + + case 'speex': + fseek($this->getid3->fp, $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR); + $this->ParseVorbisCommentsFilepointer(); + 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 { + + fseek($this->getid3->fp, max($info['avdataend'] - $this->getid3->fread_buffer_size(), 0), SEEK_SET); + $LastChunkOfOgg = strrev(fread($this->getid3->fp, $this->getid3->fread_buffer_size())); + if ($LastOggSpostion = strpos($LastChunkOfOgg, 'SggO')) { + fseek($this->getid3->fp, $info['avdataend'] - ($LastOggSpostion + strlen('SggO')), SEEK_SET); + $info['avdataend'] = ftell($this->getid3->fp); + $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; + } + + 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; + } + + function ParseOggPageHeader() { + // http://xiph.org/ogg/vorbis/doc/framing.html + $oggheader['page_start_offset'] = ftell($this->getid3->fp); // where we started from in the file + + $filedata = fread($this->getid3->fp, $this->getid3->fread_buffer_size()); + $filedataoffset = 0; + while ((substr($filedata, $filedataoffset++, 4) != 'OggS')) { + if ((ftell($this->getid3->fp) - $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 (feof($this->getid3->fp) || (($filedata .= fread($this->getid3->fp, $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']; + fseek($this->getid3->fp, $oggheader['header_end_offset'], SEEK_SET); + + return $oggheader; + } + + + function ParseVorbisCommentsFilepointer() { + $info = &$this->getid3->info; + + $OriginalOffset = ftell($this->getid3->fp); + $commentdataoffset = 0; + $VorbisCommentPage = 1; + + switch ($info['audio']['dataformat']) { + case 'vorbis': + $CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset']; // Second Ogg page, after header block + fseek($this->getid3->fp, $CommentStartOffset, SEEK_SET); + $commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments']; + $commentdata = fread($this->getid3->fp, getid3_ogg::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset); + + $commentdataoffset += (strlen('vorbis') + 1); + break; + + case 'flac': + $CommentStartOffset = $info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4; + fseek($this->getid3->fp, $CommentStartOffset, SEEK_SET); + $commentdata = fread($this->getid3->fp, $info['flac']['VORBIS_COMMENT']['raw']['block_length']); + break; + + case 'speex': + $CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset']; // Second Ogg page, after header block + fseek($this->getid3->fp, $CommentStartOffset, SEEK_SET); + $commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments']; + $commentdata = fread($this->getid3->fp, getid3_ogg::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset); + break; + + default: + return false; + break; + } + + $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 (ftell($this->getid3->fp) < ($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 .= fread($this->getid3->fp, $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); + $commentdata .= fread($this->getid3->fp, $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 .= fread($this->getid3->fp, $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); + if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) { + $info['warning'][] = 'undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.ftell($this->getid3->fp); + break; + } + $readlength = getid3_ogg::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1); + if ($readlength <= 0) { + $info['warning'][] = 'invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.ftell($this->getid3->fp); + break; + } + $commentdata .= fread($this->getid3->fp, $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] : ''); + $ThisFileInfo_ogg_comments_raw[$i]['data'] = base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']); + $ThisFileInfo_ogg_comments_raw[$i]['data_length'] = strlen($ThisFileInfo_ogg_comments_raw[$i]['data']); + + if (preg_match('#^(BM|GIF|\xFF\xD8\xFF|\x89\x50\x4E\x47\x0D\x0A\x1A\x0A|II\x2A\x00|MM\x00\x2A)#s', $ThisFileInfo_ogg_comments_raw[$i]['data'])) { + $imageinfo = array(); + $imagechunkcheck = getid3_lib::GetDataImageSize($ThisFileInfo_ogg_comments_raw[$i]['data'], $imageinfo); + unset($imageinfo); + if (!empty($imagechunkcheck)) { + $ThisFileInfo_ogg_comments_raw[$i]['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]); + if ($ThisFileInfo_ogg_comments_raw[$i]['image_mime'] && ($ThisFileInfo_ogg_comments_raw[$i]['image_mime'] != 'application/octet-stream')) { + unset($ThisFileInfo_ogg_comments_raw[$i]['value']); + } + } + } + + if (isset($ThisFileInfo_ogg_comments_raw[$i]['value'])) { + unset($ThisFileInfo_ogg_comments_raw[$i]['data']); + $info['ogg']['comments'][strtolower($ThisFileInfo_ogg_comments_raw[$i]['key'])][] = $ThisFileInfo_ogg_comments_raw[$i]['value']; + } else { + do { + if ($this->inline_attachments === false) { + // skip entirely + unset($ThisFileInfo_ogg_comments_raw[$i]['data']); + break; + } + if ($this->inline_attachments === true) { + // great + } elseif (is_int($this->inline_attachments)) { + if ($this->inline_attachments < $ThisFileInfo_ogg_comments_raw[$i]['data_length']) { + // too big, skip + $info['warning'][] = 'attachment at '.$ThisFileInfo_ogg_comments_raw[$i]['offset'].' is too large to process inline ('.number_format($ThisFileInfo_ogg_comments_raw[$i]['data_length']).' bytes)'; + unset($ThisFileInfo_ogg_comments_raw[$i]['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 '.$ThisFileInfo_ogg_comments_raw[$i]['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)'; + unset($ThisFileInfo_ogg_comments_raw[$i]['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']).'_'.$ThisFileInfo_ogg_comments_raw[$i]['offset']; + if (!file_exists($destination_filename) || is_writable($destination_filename)) { + file_put_contents($destination_filename, $ThisFileInfo_ogg_comments_raw[$i]['data']); + } else { + $info['warning'][] = 'attachment at '.$ThisFileInfo_ogg_comments_raw[$i]['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)'; + } + $ThisFileInfo_ogg_comments_raw[$i]['data_filename'] = $destination_filename; + unset($ThisFileInfo_ogg_comments_raw[$i]['data']); + } else { + $info['ogg']['comments']['picture'][] = array('data'=>$ThisFileInfo_ogg_comments_raw[$i]['data'], 'image_mime'=>$ThisFileInfo_ogg_comments_raw[$i]['image_mime']); + } + } while (false); + + } + + + } else { + + $info['warning'][] = '[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring; + + } + } + + + // 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; + } + } + } + + fseek($this->getid3->fp, $OriginalOffset, SEEK_SET); + + return true; + } + + 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); + } + + + 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; + } + + + 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 + } + +} + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/module.audio.optimfrog.php b/3rdparty/getid3/module.audio.optimfrog.php similarity index 65% rename from apps/media/getID3/getid3/module.audio.optimfrog.php rename to 3rdparty/getid3/module.audio.optimfrog.php index 3c2dfb0bd2..c1c8963837 100644 --- a/apps/media/getID3/getid3/module.audio.optimfrog.php +++ b/3rdparty/getid3/module.audio.optimfrog.php @@ -15,39 +15,42 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); -class getid3_optimfrog +class getid3_optimfrog extends getid3_handler { - function getid3_optimfrog(&$fd, &$ThisFileInfo) { - $ThisFileInfo['fileformat'] = 'ofr'; - $ThisFileInfo['audio']['dataformat'] = 'ofr'; - $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; - $ThisFileInfo['audio']['lossless'] = true; + function Analyze() { + $info = &$this->getid3->info; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $OFRheader = fread($fd, 8); + $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($fd, $ThisFileInfo); + return $this->ParseOptimFROGheader42(); } elseif (substr($OFRheader, 0, 3) == 'OFR') { - return $this->ParseOptimFROGheader45($fd, $ThisFileInfo); + return $this->ParseOptimFROGheader45(); } - $ThisFileInfo['error'][] = 'Expecting "*RIFF" or "OFR " at offset '.$ThisFileInfo['avdataoffset'].', found "'.$OFRheader.'"'; - unset($ThisFileInfo['fileformat']); + $info['error'][] = 'Expecting "*RIFF" or "OFR " at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($OFRheader).'"'; + unset($info['fileformat']); return false; } - function ParseOptimFROGheader42(&$fd, &$ThisFileInfo) { + function ParseOptimFROGheader42() { // for fileformat of v4.21 and older - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $OptimFROGheaderData = fread($fd, 45); - $ThisFileInfo['avdataoffset'] = 45; + $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); @@ -57,36 +60,46 @@ class getid3_optimfrog $OrignalRIFFdataSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 40, 4)) + 44; if ($OrignalRIFFheaderSize > $OrignalRIFFdataSize) { - $ThisFileInfo['avdataend'] -= ($OrignalRIFFheaderSize - $OrignalRIFFdataSize); - fseek($fd, $ThisFileInfo['avdataend'], SEEK_SET); - $RIFFdata .= fread($fd, $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_riff::ParseRIFFdata($RIFFdata, $ThisFileInfo); - $ThisFileInfo['audio']['encoder'] = 'OptimFROG '.$OptimFROGencoderVersion_major.'.'.$OptimFROGencoderVersion_minor; - $ThisFileInfo['audio']['channels'] = $ThisFileInfo['riff']['audio'][0]['channels']; - $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['riff']['audio'][0]['sample_rate']; - $ThisFileInfo['audio']['bits_per_sample'] = $ThisFileInfo['riff']['audio'][0]['bits_per_sample']; - $ThisFileInfo['playtime_seconds'] = $OrignalRIFFdataSize / ($ThisFileInfo['audio']['channels'] * $ThisFileInfo['audio']['sample_rate'] * ($ThisFileInfo['audio']['bits_per_sample'] / 8)); - $ThisFileInfo['audio']['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; + $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; } - function ParseOptimFROGheader45(&$fd, &$ThisFileInfo) { + function ParseOptimFROGheader45() { // for fileformat of v4.50a and higher + $info = &$this->getid3->info; $RIFFdata = ''; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - while (!feof($fd) && (ftell($fd) < $ThisFileInfo['avdataend'])) { - $BlockOffset = ftell($fd); - $BlockData = fread($fd, 8); + 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)); @@ -94,10 +107,10 @@ class getid3_optimfrog if ($BlockName == 'OFRX') { $BlockName = 'OFR '; } - if (!isset($ThisFileInfo['ofr'][$BlockName])) { - $ThisFileInfo['ofr'][$BlockName] = array(); + if (!isset($info['ofr'][$BlockName])) { + $info['ofr'][$BlockName] = array(); } - $thisfile_ofr_thisblock = &$ThisFileInfo['ofr'][$BlockName]; + $thisfile_ofr_thisblock = &$info['ofr'][$BlockName]; switch ($BlockName) { case 'OFR ': @@ -106,7 +119,7 @@ class getid3_optimfrog $thisfile_ofr_thisblock['offset'] = $BlockOffset; $thisfile_ofr_thisblock['size'] = $BlockSize; - $ThisFileInfo['audio']['encoder'] = 'OptimFROG 4.50 alpha'; + $info['audio']['encoder'] = 'OptimFROG 4.50 alpha'; switch ($BlockSize) { case 12: case 15: @@ -114,10 +127,10 @@ class getid3_optimfrog break; default: - $ThisFileInfo['warning'][] = '"'.$BlockName.'" contains more data than expected (expected 12 or 15 bytes, found '.$BlockSize.' bytes)'; + $info['warning'][] = '"'.$BlockName.'" contains more data than expected (expected 12 or 15 bytes, found '.$BlockSize.' bytes)'; break; } - $BlockData .= fread($fd, $BlockSize); + $BlockData .= fread($this->getid3->fp, $BlockSize); $thisfile_ofr_thisblock['total_samples'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 6)); $offset += 6; @@ -142,23 +155,23 @@ class getid3_optimfrog $thisfile_ofr_thisblock['speedup'] = $this->OptimFROGspeedupLookup($thisfile_ofr_thisblock['raw']['compression']); $offset += 1; - $ThisFileInfo['audio']['encoder'] = 'OptimFROG '.$thisfile_ofr_thisblock['encoder']; - $ThisFileInfo['audio']['encoder_options'] = '--mode '.$thisfile_ofr_thisblock['compression']; + $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($ThisFileInfo['filename'])) == 'ofs') { + 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. - $ThisFileInfo['audio']['dataformat'] = 'ofs'; - $ThisFileInfo['audio']['lossless'] = true; + $info['audio']['dataformat'] = 'ofs'; + $info['audio']['lossless'] = true; } } } - $ThisFileInfo['audio']['channels'] = $thisfile_ofr_thisblock['channels']; - $ThisFileInfo['audio']['sample_rate'] = $thisfile_ofr_thisblock['sample_rate']; - $ThisFileInfo['audio']['bits_per_sample'] = $this->OptimFROGbitsPerSampleTypeLookup($thisfile_ofr_thisblock['raw']['sample_type']); + $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; @@ -168,13 +181,13 @@ class getid3_optimfrog $COMPdata['offset'] = $BlockOffset; $COMPdata['size'] = $BlockSize; - if ($ThisFileInfo['avdataoffset'] == 0) { - $ThisFileInfo['avdataoffset'] = $BlockOffset; + 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($fd, 14); - fseek($fd, $BlockSize - 14, SEEK_CUR); + $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; @@ -190,7 +203,7 @@ class getid3_optimfrog //$COMPdata['algorithm'] = OptimFROGalgorithmNameLookup($COMPdata['raw']['algorithm_id']); $offset += 2; - if ($ThisFileInfo['ofr']['OFR ']['size'] > 12) { + if ($info['ofr']['OFR ']['size'] > 12) { // OFR 4.504b or higher $COMPdata['raw']['encoder_id'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 2)); @@ -211,7 +224,7 @@ class getid3_optimfrog $thisfile_ofr_thisblock['offset'] = $BlockOffset; $thisfile_ofr_thisblock['size'] = $BlockSize; - $RIFFdata .= fread($fd, $BlockSize); + $RIFFdata .= fread($this->getid3->fp, $BlockSize); break; case 'TAIL': @@ -219,7 +232,7 @@ class getid3_optimfrog $thisfile_ofr_thisblock['size'] = $BlockSize; if ($BlockSize > 0) { - $RIFFdata .= fread($fd, $BlockSize); + $RIFFdata .= fread($this->getid3->fp, $BlockSize); } break; @@ -229,7 +242,7 @@ class getid3_optimfrog $thisfile_ofr_thisblock['offset'] = $BlockOffset; $thisfile_ofr_thisblock['size'] = $BlockSize; - fseek($fd, $BlockSize, SEEK_CUR); + fseek($this->getid3->fp, $BlockSize, SEEK_CUR); break; @@ -238,9 +251,9 @@ class getid3_optimfrog $thisfile_ofr_thisblock['offset'] = $BlockOffset; $thisfile_ofr_thisblock['size'] = $BlockSize; - $ThisFileInfo['warning'][] = 'APEtag processing inside OptimFROG not supported in this version ('.GETID3_VERSION.') of getID3()'; + $info['warning'][] = 'APEtag processing inside OptimFROG not supported in this version ('.$this->getid3->version().') of getID3()'; - fseek($fd, $BlockSize, SEEK_CUR); + fseek($this->getid3->fp, $BlockSize, SEEK_CUR); break; @@ -252,14 +265,14 @@ class getid3_optimfrog if ($BlockSize == 16) { - $thisfile_ofr_thisblock['md5_binary'] = fread($fd, $BlockSize); + $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); - $ThisFileInfo['md5_data_source'] = $thisfile_ofr_thisblock['md5_string']; + $info['md5_data_source'] = $thisfile_ofr_thisblock['md5_string']; } else { - $ThisFileInfo['warning'][] = 'Expecting block size of 16 in "MD5 " chunk, found '.$BlockSize.' instead'; - fseek($fd, $BlockSize, SEEK_CUR); + $info['warning'][] = 'Expecting block size of 16 in "MD5 " chunk, found '.$BlockSize.' instead'; + fseek($this->getid3->fp, $BlockSize, SEEK_CUR); } break; @@ -269,29 +282,38 @@ class getid3_optimfrog $thisfile_ofr_thisblock['offset'] = $BlockOffset; $thisfile_ofr_thisblock['size'] = $BlockSize; - $ThisFileInfo['warning'][] = 'Unhandled OptimFROG block type "'.$BlockName.'" at offset '.$thisfile_ofr_thisblock['offset']; - fseek($fd, $BlockSize, SEEK_CUR); + $info['warning'][] = 'Unhandled OptimFROG block type "'.$BlockName.'" at offset '.$thisfile_ofr_thisblock['offset']; + fseek($this->getid3->fp, $BlockSize, SEEK_CUR); break; } } - if (isset($ThisFileInfo['ofr']['TAIL']['offset'])) { - $ThisFileInfo['avdataend'] = $ThisFileInfo['ofr']['TAIL']['offset']; + if (isset($info['ofr']['TAIL']['offset'])) { + $info['avdataend'] = $info['ofr']['TAIL']['offset']; } - $ThisFileInfo['playtime_seconds'] = (float) $ThisFileInfo['ofr']['OFR ']['total_samples'] / ($ThisFileInfo['audio']['channels'] * $ThisFileInfo['audio']['sample_rate']); - $ThisFileInfo['audio']['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; + $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_riff::ParseRIFFdata($RIFFdata, $ThisFileInfo); + + $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; } - function OptimFROGsampleTypeLookup($SampleType) { + static function OptimFROGsampleTypeLookup($SampleType) { static $OptimFROGsampleTypeLookup = array( 0 => 'unsigned int (8-bit)', 1 => 'signed int (8-bit)', @@ -308,7 +330,7 @@ class getid3_optimfrog return (isset($OptimFROGsampleTypeLookup[$SampleType]) ? $OptimFROGsampleTypeLookup[$SampleType] : false); } - function OptimFROGbitsPerSampleTypeLookup($SampleType) { + static function OptimFROGbitsPerSampleTypeLookup($SampleType) { static $OptimFROGbitsPerSampleTypeLookup = array( 0 => 8, 1 => 8, @@ -325,7 +347,7 @@ class getid3_optimfrog return (isset($OptimFROGbitsPerSampleTypeLookup[$SampleType]) ? $OptimFROGbitsPerSampleTypeLookup[$SampleType] : false); } - function OptimFROGchannelConfigurationLookup($ChannelConfiguration) { + static function OptimFROGchannelConfigurationLookup($ChannelConfiguration) { static $OptimFROGchannelConfigurationLookup = array( 0 => 'mono', 1 => 'stereo' @@ -333,7 +355,7 @@ class getid3_optimfrog return (isset($OptimFROGchannelConfigurationLookup[$ChannelConfiguration]) ? $OptimFROGchannelConfigurationLookup[$ChannelConfiguration] : false); } - function OptimFROGchannelConfigNumChannelsLookup($ChannelConfiguration) { + static function OptimFROGchannelConfigNumChannelsLookup($ChannelConfiguration) { static $OptimFROGchannelConfigNumChannelsLookup = array( 0 => 1, 1 => 2 @@ -343,13 +365,13 @@ class getid3_optimfrog - // function OptimFROGalgorithmNameLookup($AlgorithID) { + // static function OptimFROGalgorithmNameLookup($AlgorithID) { // static $OptimFROGalgorithmNameLookup = array(); // return (isset($OptimFROGalgorithmNameLookup[$AlgorithID]) ? $OptimFROGalgorithmNameLookup[$AlgorithID] : false); // } - function OptimFROGencoderNameLookup($EncoderID) { + static function OptimFROGencoderNameLookup($EncoderID) { // version = (encoderID >> 4) + 4500 // system = encoderID & 0xF @@ -364,7 +386,7 @@ class getid3_optimfrog return $EncoderVersion.' ('.(isset($OptimFROGencoderSystemLookup[$EncoderSystemID]) ? $OptimFROGencoderSystemLookup[$EncoderSystemID] : 'undefined encoder type (0x'.dechex($EncoderSystemID).')').')'; } - function OptimFROGcompressionLookup($CompressionID) { + static function OptimFROGcompressionLookup($CompressionID) { // mode = compression >> 3 // speedup = compression & 0x07 @@ -386,7 +408,7 @@ class getid3_optimfrog return (isset($OptimFROGencoderModeLookup[$CompressionModeID]) ? $OptimFROGencoderModeLookup[$CompressionModeID] : 'undefined mode (0x'.str_pad(dechex($CompressionModeID), 2, '0', STR_PAD_LEFT).')'); } - function OptimFROGspeedupLookup($CompressionID) { + static function OptimFROGspeedupLookup($CompressionID) { // mode = compression >> 3 // speedup = compression & 0x07 @@ -398,7 +420,6 @@ class getid3_optimfrog 0x01 => '2x', 0x02 => '4x' ); - return (isset($OptimFROGencoderSpeedupLookup[$CompressionSpeedupID]) ? $OptimFROGencoderSpeedupLookup[$CompressionSpeedupID] : 'undefined mode (0x'.dechex($CompressionSpeedupID)); } diff --git a/3rdparty/getid3/module.audio.rkau.php b/3rdparty/getid3/module.audio.rkau.php new file mode 100644 index 0000000000..c442076275 --- /dev/null +++ b/3rdparty/getid3/module.audio.rkau.php @@ -0,0 +1,94 @@ + // +// 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 +{ + + 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; + + } + + + 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; + } + +} + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/module.audio.shorten.php b/3rdparty/getid3/module.audio.shorten.php similarity index 54% rename from apps/media/getID3/getid3/module.audio.shorten.php rename to 3rdparty/getid3/module.audio.shorten.php index a9eb1ab1cc..7b3d312ea2 100644 --- a/apps/media/getID3/getid3/module.audio.shorten.php +++ b/3rdparty/getid3/module.audio.shorten.php @@ -14,36 +14,39 @@ ///////////////////////////////////////////////////////////////// -class getid3_shorten +class getid3_shorten extends getid3_handler { - function getid3_shorten(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $ShortenHeader = fread($fd, 8); - if (substr($ShortenHeader, 0, 4) != 'ajkg') { - $ThisFileInfo['error'][] = 'Expecting "ajkg" at offset '.$ThisFileInfo['avdataoffset'].', found "'.substr($ShortenHeader, 0, 4).'"'; + $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; } - $ThisFileInfo['fileformat'] = 'shn'; - $ThisFileInfo['audio']['dataformat'] = 'shn'; - $ThisFileInfo['audio']['lossless'] = true; - $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; + $info['fileformat'] = 'shn'; + $info['audio']['dataformat'] = 'shn'; + $info['audio']['lossless'] = true; + $info['audio']['bitrate_mode'] = 'vbr'; - $ThisFileInfo['shn']['version'] = getid3_lib::LittleEndian2Int(substr($ShortenHeader, 4, 1)); + $info['shn']['version'] = getid3_lib::LittleEndian2Int(substr($ShortenHeader, 4, 1)); - fseek($fd, $ThisFileInfo['avdataend'] - 12, SEEK_SET); - $SeekTableSignatureTest = fread($fd, 12); - $ThisFileInfo['shn']['seektable']['present'] = (bool) (substr($SeekTableSignatureTest, 4, 8) == 'SHNAMPSK'); - if ($ThisFileInfo['shn']['seektable']['present']) { - $ThisFileInfo['shn']['seektable']['length'] = getid3_lib::LittleEndian2Int(substr($SeekTableSignatureTest, 0, 4)); - $ThisFileInfo['shn']['seektable']['offset'] = $ThisFileInfo['avdataend'] - $ThisFileInfo['shn']['seektable']['length']; - fseek($fd, $ThisFileInfo['shn']['seektable']['offset'], SEEK_SET); - $SeekTableMagic = fread($fd, 4); - if ($SeekTableMagic != 'SEEK') { + 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) { - $ThisFileInfo['error'][] = 'Expecting "SEEK" at offset '.$ThisFileInfo['shn']['seektable']['offset'].', found "'.$SeekTableMagic.'"'; + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['shn']['seektable']['offset'].', found "'.getid3_lib::PrintHexBytes($SeekTableMagic).'"'; return false; } else { @@ -64,11 +67,11 @@ class getid3_shorten // long Offset1[4]; // }TSeekEntry; - $SeekTableData = fread($fd, $ThisFileInfo['shn']['seektable']['length'] - 16); - $ThisFileInfo['shn']['seektable']['entry_count'] = floor(strlen($SeekTableData) / 80); - //$ThisFileInfo['shn']['seektable']['entries'] = array(); + $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 < $ThisFileInfo['shn']['seektable']['entry_count']; $i++) { + //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)); @@ -101,16 +104,16 @@ class getid3_shorten // $SeekTableEntry['offset1'][$j] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4)); // $SeekTableOffset += 4; // } - // - // $ThisFileInfo['shn']['seektable']['entries'][] = $SeekTableEntry; + // + // $info['shn']['seektable']['entries'][] = $SeekTableEntry; //} } } - if ((bool) ini_get('safe_mode')) { - $ThisFileInfo['error'][] = 'PHP running in Safe Mode - backtick operator not available, cannot run shntool to analyze Shorten files'; + 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; } @@ -119,24 +122,24 @@ class getid3_shorten $RequiredFiles = array('shorten.exe', 'cygwin1.dll', 'head.exe'); foreach ($RequiredFiles as $required_file) { if (!is_readable(GETID3_HELPERAPPSDIR.$required_file)) { - $ThisFileInfo['error'][] = GETID3_HELPERAPPSDIR.$required_file.' does not exist'; + $info['error'][] = GETID3_HELPERAPPSDIR.$required_file.' does not exist'; return false; } } - $commandline = GETID3_HELPERAPPSDIR.'shorten.exe -x "'.$ThisFileInfo['filenamepath'].'" - | '.GETID3_HELPERAPPSDIR.'head.exe -c 64'; + $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) { - $ThisFileInfo['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($ThisFileInfo['filenamepath']).' - | head -c 64'; + 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'; } @@ -148,26 +151,26 @@ class getid3_shorten $fmt_size = getid3_lib::LittleEndian2Int(substr($output, 16, 4)); $DecodedWAVFORMATEX = getid3_riff::RIFFparseWAVEFORMATex(substr($output, 20, $fmt_size)); - $ThisFileInfo['audio']['channels'] = $DecodedWAVFORMATEX['channels']; - $ThisFileInfo['audio']['bits_per_sample'] = $DecodedWAVFORMATEX['bits_per_sample']; - $ThisFileInfo['audio']['sample_rate'] = $DecodedWAVFORMATEX['sample_rate']; + $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') { - $ThisFileInfo['playtime_seconds'] = getid3_lib::LittleEndian2Int(substr($output, 20 + 4 + $fmt_size, 4)) / $DecodedWAVFORMATEX['raw']['nAvgBytesPerSec']; + $info['playtime_seconds'] = getid3_lib::LittleEndian2Int(substr($output, 20 + 4 + $fmt_size, 4)) / $DecodedWAVFORMATEX['raw']['nAvgBytesPerSec']; } else { - $ThisFileInfo['error'][] = 'shorten failed to decode DATA chunk to expected location, cannot determine playtime'; + $info['error'][] = 'shorten failed to decode DATA chunk to expected location, cannot determine playtime'; return false; } - $ThisFileInfo['audio']['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) / $ThisFileInfo['playtime_seconds']) * 8; + $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8; } else { - $ThisFileInfo['error'][] = 'shorten failed to decode file to WAV for parsing'; + $info['error'][] = 'shorten failed to decode file to WAV for parsing'; return false; } diff --git a/3rdparty/getid3/module.audio.tta.php b/3rdparty/getid3/module.audio.tta.php new file mode 100644 index 0000000000..1c646ee676 --- /dev/null +++ b/3rdparty/getid3/module.audio.tta.php @@ -0,0 +1,109 @@ + // +// 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 +{ + + 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::RIFFwFormatTagLookup() + $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; + } + +} + + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/module.audio.voc.php b/3rdparty/getid3/module.audio.voc.php similarity index 77% rename from apps/media/getID3/getid3/module.audio.voc.php rename to 3rdparty/getid3/module.audio.voc.php index e93b44fa61..1186a4cc03 100644 --- a/apps/media/getID3/getid3/module.audio.voc.php +++ b/3rdparty/getid3/module.audio.voc.php @@ -14,26 +14,28 @@ ///////////////////////////////////////////////////////////////// -class getid3_voc +class getid3_voc extends getid3_handler { - function getid3_voc(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - $OriginalAVdataOffset = $ThisFileInfo['avdataoffset']; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $VOCheader = fread($fd, 26); + $OriginalAVdataOffset = $info['avdataoffset']; + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $VOCheader = fread($this->getid3->fp, 26); - if (substr($VOCheader, 0, 19) != 'Creative Voice File') { - $ThisFileInfo['error'][] = 'Expecting "Creative Voice File" at offset '.$ThisFileInfo['avdataoffset'].', found "'.substr($VOCheader, 0, 19).'"'; + $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 = &$ThisFileInfo['audio']; - $ThisFileInfo['voc'] = array(); - $thisfile_voc = &$ThisFileInfo['voc']; + $thisfile_audio = &$info['audio']; + $info['voc'] = array(); + $thisfile_voc = &$info['voc']; - $ThisFileInfo['fileformat'] = 'voc'; + $info['fileformat'] = 'voc'; $thisfile_audio['dataformat'] = 'voc'; $thisfile_audio['bitrate_mode'] = 'cbr'; $thisfile_audio['lossless'] = true; @@ -54,24 +56,24 @@ class getid3_voc do { - $BlockOffset = ftell($fd); - $BlockData = fread($fd, 4); + $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(); - @$thisfile_voc['blocktypes'][$BlockType]++; + 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($fd, 2); - if ($ThisFileInfo['avdataoffset'] <= $OriginalAVdataOffset) { - $ThisFileInfo['avdataoffset'] = ftell($fd); + $BlockData .= fread($this->getid3->fp, 2); + if ($info['avdataoffset'] <= $OriginalAVdataOffset) { + $info['avdataoffset'] = ftell($this->getid3->fp); } - fseek($fd, $BlockSize - 2, SEEK_CUR); + 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)); @@ -94,11 +96,11 @@ class getid3_voc case 6: // Repeat case 7: // End repeat // nothing useful, just skip - fseek($fd, $BlockSize, SEEK_CUR); + fseek($this->getid3->fp, $BlockSize, SEEK_CUR); break; case 8: // Extended - $BlockData .= fread($fd, 4); + $BlockData .= fread($this->getid3->fp, 4); //00-01 Time Constant: // Mono: 65536 - (256000000 / sample_rate) @@ -112,11 +114,11 @@ class getid3_voc break; case 9: // data block that supersedes blocks 1 and 8. Used for stereo, 16 bit - $BlockData .= fread($fd, 12); - if ($ThisFileInfo['avdataoffset'] <= $OriginalAVdataOffset) { - $ThisFileInfo['avdataoffset'] = ftell($fd); + $BlockData .= fread($this->getid3->fp, 12); + if ($info['avdataoffset'] <= $OriginalAVdataOffset) { + $info['avdataoffset'] = ftell($this->getid3->fp); } - fseek($fd, $BlockSize - 12, SEEK_CUR); + 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)); @@ -134,8 +136,8 @@ class getid3_voc break; default: - $ThisFileInfo['warning'][] = 'Unhandled block type "'.$BlockType.'" at offset '.$BlockOffset; - fseek($fd, $BlockSize, SEEK_CUR); + $info['warning'][] = 'Unhandled block type "'.$BlockType.'" at offset '.$BlockOffset; + fseek($this->getid3->fp, $BlockSize, SEEK_CUR); break; } @@ -146,16 +148,16 @@ class getid3_voc $thisfile_voc['blocks'][] = $ThisBlock; } - } while (!feof($fd) && ($BlockType != 0)); + } while (!feof($this->getid3->fp) && ($BlockType != 0)); // Terminator block doesn't have size field, so seek back 3 spaces - fseek($fd, -3, SEEK_CUR); + fseek($this->getid3->fp, -3, SEEK_CUR); ksort($thisfile_voc['blocktypes']); if (!empty($thisfile_voc['compressed_bits_per_sample'])) { - $ThisFileInfo['playtime_seconds'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / ($thisfile_voc['compressed_bits_per_sample'] * $thisfile_audio['channels'] * $thisfile_audio['sample_rate']); - $thisfile_audio['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; + $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; diff --git a/apps/media/getID3/getid3/module.audio.vqf.php b/3rdparty/getid3/module.audio.vqf.php similarity index 55% rename from apps/media/getID3/getid3/module.audio.vqf.php rename to 3rdparty/getid3/module.audio.vqf.php index 49d4e8510e..dc6ff5ecc4 100644 --- a/apps/media/getID3/getid3/module.audio.vqf.php +++ b/3rdparty/getid3/module.audio.vqf.php @@ -14,58 +14,61 @@ ///////////////////////////////////////////////////////////////// -class getid3_vqf +class getid3_vqf extends getid3_handler { - function getid3_vqf(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; + // based loosely on code from TTwinVQ by Jurgen Faul // http://jfaul.de/atl or http://j-faul.virtualave.net/atl/atl.html - $ThisFileInfo['fileformat'] = 'vqf'; - $ThisFileInfo['audio']['dataformat'] = 'vqf'; - $ThisFileInfo['audio']['bitrate_mode'] = 'cbr'; - $ThisFileInfo['audio']['lossless'] = false; + $info['fileformat'] = 'vqf'; + $info['audio']['dataformat'] = 'vqf'; + $info['audio']['bitrate_mode'] = 'cbr'; + $info['audio']['lossless'] = false; // shortcut - $ThisFileInfo['vqf']['raw'] = array(); - $thisfile_vqf = &$ThisFileInfo['vqf']; + $info['vqf']['raw'] = array(); + $thisfile_vqf = &$info['vqf']; $thisfile_vqf_raw = &$thisfile_vqf['raw']; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $VQFheaderData = fread($fd, 16); + 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); - if ($thisfile_vqf_raw['header_tag'] != 'TWIN') { - $ThisFileInfo['error'][] = 'Expecting "TWIN" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$thisfile_vqf_raw['header_tag'].'"'; - unset($ThisFileInfo['vqf']); - unset($ThisFileInfo['fileformat']); + $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); + $thisfile_vqf_raw['version'] = substr($VQFheaderData, $offset, 8); $offset += 8; - $thisfile_vqf_raw['size'] = getid3_lib::BigEndian2Int(substr($VQFheaderData, $offset, 4)); + $thisfile_vqf_raw['size'] = getid3_lib::BigEndian2Int(substr($VQFheaderData, $offset, 4)); $offset += 4; - while (ftell($fd) < $ThisFileInfo['avdataend']) { + while (ftell($this->getid3->fp) < $info['avdataend']) { - $ChunkBaseOffset = ftell($fd); + $ChunkBaseOffset = ftell($this->getid3->fp); $chunkoffset = 0; - $ChunkData = fread($fd, 8); + $ChunkData = fread($this->getid3->fp, 8); $ChunkName = substr($ChunkData, $chunkoffset, 4); if ($ChunkName == 'DATA') { - $ThisFileInfo['avdataoffset'] = $ChunkBaseOffset; + $info['avdataoffset'] = $ChunkBaseOffset; break; } $chunkoffset += 4; $ChunkSize = getid3_lib::BigEndian2Int(substr($ChunkData, $chunkoffset, 4)); $chunkoffset += 4; - if ($ChunkSize > ($ThisFileInfo['avdataend'] - ftell($fd))) { - $ThisFileInfo['error'][] = 'Invalid chunk size ('.$ChunkSize.') for chunk "'.$ChunkName.'" at offset '.$ChunkBaseOffset; + 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($fd, $ChunkSize); + $ChunkData .= fread($this->getid3->fp, $ChunkSize); } switch ($ChunkName) { @@ -83,13 +86,13 @@ class getid3_vqf $thisfile_vqf_COMM['security_level'] = getid3_lib::BigEndian2Int(substr($ChunkData, $chunkoffset, 4)); $chunkoffset += 4; - $ThisFileInfo['audio']['channels'] = $thisfile_vqf_COMM['channel_mode'] + 1; - $ThisFileInfo['audio']['sample_rate'] = $this->VQFchannelFrequencyLookup($thisfile_vqf_COMM['sample_rate']); - $ThisFileInfo['audio']['bitrate'] = $thisfile_vqf_COMM['bitrate'] * 1000; - $ThisFileInfo['audio']['encoder_options'] = 'CBR' . ceil($ThisFileInfo['audio']['bitrate']/1000); + $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 ($ThisFileInfo['audio']['bitrate'] == 0) { - $ThisFileInfo['error'][] = 'Corrupt VQF file: bitrate_audio == zero'; + if ($info['audio']['bitrate'] == 0) { + $info['error'][] = 'Corrupt VQF file: bitrate_audio == zero'; return false; } break; @@ -108,23 +111,23 @@ class getid3_vqf break; default: - $ThisFileInfo['warning'][] = 'Unhandled chunk type "'.$ChunkName.'" at offset '.$ChunkBaseOffset; + $info['warning'][] = 'Unhandled chunk type "'.$ChunkName.'" at offset '.$ChunkBaseOffset; break; } } - $ThisFileInfo['playtime_seconds'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['audio']['bitrate']; + $info['playtime_seconds'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate']; - if (isset($thisfile_vqf['DSIZ']) && (($thisfile_vqf['DSIZ'] != ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'] - strlen('DATA'))))) { + if (isset($thisfile_vqf['DSIZ']) && (($thisfile_vqf['DSIZ'] != ($info['avdataend'] - $info['avdataoffset'] - strlen('DATA'))))) { switch ($thisfile_vqf['DSIZ']) { case 0: case 1: - $ThisFileInfo['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'; - $ThisFileInfo['audio']['encoder'] = 'Ahead Nero'; + $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: - $ThisFileInfo['warning'][] = 'Probable corrupted file - should be '.$thisfile_vqf['DSIZ'].' bytes, actually '.($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'] - strlen('DATA')); + $info['warning'][] = 'Probable corrupted file - should be '.$thisfile_vqf['DSIZ'].' bytes, actually '.($info['avdataend'] - $info['avdataoffset'] - strlen('DATA')); break; } } diff --git a/3rdparty/getid3/module.audio.wavpack.php b/3rdparty/getid3/module.audio.wavpack.php new file mode 100644 index 0000000000..6ab5b43867 --- /dev/null +++ b/3rdparty/getid3/module.audio.wavpack.php @@ -0,0 +1,400 @@ + // +// 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 +{ + + 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->RIFFcommentsParse($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; + } + + + 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] : ''); + } + +} + + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/module.graphic.bmp.php b/3rdparty/getid3/module.graphic.bmp.php similarity index 89% rename from apps/media/getID3/getid3/module.graphic.bmp.php rename to 3rdparty/getid3/module.graphic.bmp.php index ea8a119b5a..5ac671fbca 100644 --- a/apps/media/getID3/getid3/module.graphic.bmp.php +++ b/3rdparty/getid3/module.graphic.bmp.php @@ -14,16 +14,19 @@ ///////////////////////////////////////////////////////////////// -class getid3_bmp +class getid3_bmp extends getid3_handler { + var $ExtractPalette = false; + var $ExtractData = false; - function getid3_bmp(&$fd, &$ThisFileInfo, $ExtractPalette=false, $ExtractData=false) { + function Analyze() { + $info = &$this->getid3->info; - // shortcuts - $ThisFileInfo['bmp']['header']['raw'] = array(); - $thisfile_bmp = &$ThisFileInfo['bmp']; - $thisfile_bmp_header = &$thisfile_bmp['header']; - $thisfile_bmp_header_raw = &$thisfile_bmp_header['raw']; + // 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 @@ -33,17 +36,18 @@ class getid3_bmp // WORD bfReserved2; // DWORD bfOffBits; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $offset = 0; - $BMPheader = fread($fd, 14 + 40); + $BMPheader = fread($this->getid3->fp, 14 + 40); $thisfile_bmp_header_raw['identifier'] = substr($BMPheader, $offset, 2); $offset += 2; - if ($thisfile_bmp_header_raw['identifier'] != 'BM') { - $ThisFileInfo['error'][] = 'Expecting "BM" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$thisfile_bmp_header_raw['identifier'].'"'; - unset($ThisFileInfo['fileformat']); - unset($ThisFileInfo['bmp']); + $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; } @@ -81,16 +85,16 @@ class getid3_bmp $thisfile_bmp['type_os'] = 'Windows'; $thisfile_bmp['type_version'] = 5; } else { - $ThisFileInfo['error'][] = 'Unknown BMP subtype (or not a BMP file)'; - unset($ThisFileInfo['fileformat']); - unset($ThisFileInfo['bmp']); + $info['error'][] = 'Unknown BMP subtype (or not a BMP file)'; + unset($info['fileformat']); + unset($info['bmp']); return false; } - $ThisFileInfo['fileformat'] = 'bmp'; - $ThisFileInfo['video']['dataformat'] = 'bmp'; - $ThisFileInfo['video']['lossless'] = true; - $ThisFileInfo['video']['pixel_aspect_ratio'] = (float) 1; + $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') { @@ -112,10 +116,10 @@ class getid3_bmp $thisfile_bmp_header_raw['bits_per_pixel'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2)); $offset += 2; - $ThisFileInfo['video']['resolution_x'] = $thisfile_bmp_header_raw['width']; - $ThisFileInfo['video']['resolution_y'] = $thisfile_bmp_header_raw['height']; - $ThisFileInfo['video']['codec'] = 'BI_RGB '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit'; - $ThisFileInfo['video']['bits_per_sample'] = $thisfile_bmp_header_raw['bits_per_pixel']; + $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 */ @@ -162,9 +166,9 @@ class getid3_bmp $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']); + $thisfile_bmp_header['compression'] = $this->BMPcompressionOS2Lookup($thisfile_bmp_header_raw['compression']); - $ThisFileInfo['video']['codec'] = $thisfile_bmp_header['compression'].' '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit'; + $info['video']['codec'] = $thisfile_bmp_header['compression'].' '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit'; } } elseif ($thisfile_bmp['type_os'] == 'Windows') { @@ -209,14 +213,14 @@ class getid3_bmp $offset += 4; $thisfile_bmp_header['compression'] = $this->BMPcompressionWindowsLookup($thisfile_bmp_header_raw['compression']); - $ThisFileInfo['video']['resolution_x'] = $thisfile_bmp_header_raw['width']; - $ThisFileInfo['video']['resolution_y'] = $thisfile_bmp_header_raw['height']; - $ThisFileInfo['video']['codec'] = $thisfile_bmp_header['compression'].' '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit'; - $ThisFileInfo['video']['bits_per_sample'] = $thisfile_bmp_header_raw['bits_per_pixel']; + $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($fd, 44); + $BMPheader .= fread($this->getid3->fp, 44); // BITMAPV4HEADER - [44 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_2k1e.asp // Win95+, WinNT4.0+ @@ -258,7 +262,7 @@ class getid3_bmp } if ($thisfile_bmp['type_version'] >= 5) { - $BMPheader .= fread($fd, 16); + $BMPheader .= fread($this->getid3->fp, 16); // BITMAPV5HEADER - [16 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_7c36.asp // Win98+, Win2000+ @@ -278,13 +282,13 @@ class getid3_bmp } else { - $ThisFileInfo['error'][] = 'Unknown BMP format in header.'; + $info['error'][] = 'Unknown BMP format in header.'; return false; } - if ($ExtractPalette || $ExtractData) { + 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']); @@ -292,7 +296,7 @@ class getid3_bmp $PaletteEntries = $thisfile_bmp_header_raw['colors_used']; } if ($PaletteEntries > 0) { - $BMPpalette = fread($fd, 4 * $PaletteEntries); + $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 @@ -313,12 +317,13 @@ class getid3_bmp } } - if ($ExtractData) { - fseek($fd, $thisfile_bmp_header_raw['data_offset'], SEEK_SET); + 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($fd, $thisfile_bmp_header_raw['height'] * $RowByteLength); + $BMPpixelData = fread($this->getid3->fp, $thisfile_bmp_header_raw['height'] * $RowByteLength); $pixeldataoffset = 0; - switch (@$thisfile_bmp_header_raw['compression']) { + $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']) { @@ -400,7 +405,7 @@ class getid3_bmp break; default: - $ThisFileInfo['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data'; + $info['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data'; break; } break; @@ -475,7 +480,7 @@ class getid3_bmp break; default: - $ThisFileInfo['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data'; + $info['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data'; break; } break; @@ -564,7 +569,7 @@ class getid3_bmp break; default: - $ThisFileInfo['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data'; + $info['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data'; break; } break; @@ -604,14 +609,14 @@ class getid3_bmp break; default: - $ThisFileInfo['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data'; + $info['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data'; break; } break; default: // unhandled compression type - $ThisFileInfo['error'][] = 'Unknown/unhandled compression type value ('.$thisfile_bmp_header_raw['compression'].') - cannot decompress pixel data'; + $info['error'][] = 'Unknown/unhandled compression type value ('.$thisfile_bmp_header_raw['compression'].') - cannot decompress pixel data'; break; } } diff --git a/3rdparty/getid3/module.graphic.efax.php b/3rdparty/getid3/module.graphic.efax.php new file mode 100644 index 0000000000..2a57302e15 --- /dev/null +++ b/3rdparty/getid3/module.graphic.efax.php @@ -0,0 +1,53 @@ + // +// 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 +{ + + 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; + } + +} + + +?> \ No newline at end of file diff --git a/3rdparty/getid3/module.graphic.gif.php b/3rdparty/getid3/module.graphic.gif.php new file mode 100644 index 0000000000..0b3e1379ff --- /dev/null +++ b/3rdparty/getid3/module.graphic.gif.php @@ -0,0 +1,184 @@ + // +// 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 +{ + + 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).'
'; +// $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.'
'; +// +// $NextValue = $this->GetLSBits($CurrentCodeSize); +// echo 'First Color: '.$NextValue.'
'; +// +// $Prefix = $NextValue; +//$i = 0; +// while ($i++ < 20) { +// $NextValue = $this->GetLSBits($CurrentCodeSize); +// echo $NextValue.'
'; +// } +//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; + } + + + 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; + } + +} + + +?> \ No newline at end of file diff --git a/3rdparty/getid3/module.graphic.jpg.php b/3rdparty/getid3/module.graphic.jpg.php new file mode 100644 index 0000000000..153ebcd0e6 --- /dev/null +++ b/3rdparty/getid3/module.graphic.jpg.php @@ -0,0 +1,338 @@ + // +// 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 +{ + + + 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['jpg']['exif'] = @exif_read_data($info['filenamepath'], '', 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']['GPSAltitude'])) { + $direction_multiplier = ((isset($info['jpg']['exif']['GPS']['GPSAltitudeRef']) && ($info['jpg']['exif']['GPS']['GPSAltitudeRef'] === chr(1))) ? -1 : 1); + $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) { + list($subsection, $tagname) = explode(':', $key); + $info['xmp'][$subsection][$tagname] = $this->CastAsAppropriate($value); + } + } + } + + if (!$returnOK) { + unset($info['fileformat']); + return false; + } + return true; + } + + + 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; + } + + + 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] : ''); + } + + + 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); + } + +} + + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/module.graphic.pcd.php b/3rdparty/getid3/module.graphic.pcd.php similarity index 67% rename from apps/media/getID3/getid3/module.graphic.pcd.php rename to 3rdparty/getid3/module.graphic.pcd.php index 60efabdf61..dcbc676363 100644 --- a/apps/media/getID3/getid3/module.graphic.pcd.php +++ b/3rdparty/getid3/module.graphic.pcd.php @@ -14,34 +14,38 @@ ///////////////////////////////////////////////////////////////// -class getid3_pcd +class getid3_pcd extends getid3_handler { - function getid3_pcd(&$fd, &$ThisFileInfo, $ExtractData=0) { - $ThisFileInfo['fileformat'] = 'pcd'; - $ThisFileInfo['video']['dataformat'] = 'pcd'; - $ThisFileInfo['video']['lossless'] = false; + var $ExtractData = 0; + + function Analyze() { + $info = &$this->getid3->info; + + $info['fileformat'] = 'pcd'; + $info['video']['dataformat'] = 'pcd'; + $info['video']['lossless'] = false; - fseek($fd, $ThisFileInfo['avdataoffset'] + 72, SEEK_SET); + fseek($this->getid3->fp, $info['avdataoffset'] + 72, SEEK_SET); - $PCDflags = fread($fd, 1); + $PCDflags = fread($this->getid3->fp, 1); $PCDisVertical = ((ord($PCDflags) & 0x01) ? true : false); if ($PCDisVertical) { - $ThisFileInfo['video']['resolution_x'] = 3072; - $ThisFileInfo['video']['resolution_y'] = 2048; + $info['video']['resolution_x'] = 3072; + $info['video']['resolution_y'] = 2048; } else { - $ThisFileInfo['video']['resolution_x'] = 2048; - $ThisFileInfo['video']['resolution_y'] = 3072; + $info['video']['resolution_x'] = 2048; + $info['video']['resolution_y'] = 3072; } - if ($ExtractData > 3) { + if ($this->ExtractData > 3) { - $ThisFileInfo['error'][] = 'Cannot extract PSD image data for detail levels above BASE (3)'; + $info['error'][] = 'Cannot extract PSD image data for detail levels above BASE (level-3) because encrypted with Kodak-proprietary compression/encryption.'; - } elseif ($ExtractData > 0) { + } elseif ($this->ExtractData > 0) { $PCD_levels[1] = array( 192, 128, 0x02000); // BASE/16 $PCD_levels[2] = array( 384, 256, 0x0B800); // BASE/4 @@ -52,7 +56,7 @@ class getid3_pcd list($PCD_width, $PCD_height, $PCD_dataOffset) = $PCD_levels[3]; - fseek($fd, $ThisFileInfo['avdataoffset'] + $PCD_dataOffset, SEEK_SET); + 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. @@ -61,18 +65,18 @@ class getid3_pcd // 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($fd, $PCD_width); - $PCD_data_Y2 = fread($fd, $PCD_width); - $PCD_data_Cb = fread($fd, intval(round($PCD_width / 2))); - $PCD_data_Cr = fread($fd, intval(round($PCD_width / 2))); + $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) { - $ThisFileInfo['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)})); - $ThisFileInfo['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)})); + $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 { - $ThisFileInfo['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)})); - $ThisFileInfo['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)})); + $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)})); } } } @@ -86,7 +90,7 @@ class getid3_pcd // $BMPinfo['resolution_x'] = $PCD_width; // $BMPinfo['resolution_y'] = $PCD_height; //} - //$BMPinfo['bmp']['data'] = $ThisFileInfo['pcd']['data']; + //$BMPinfo['bmp']['data'] = $info['pcd']['data']; //getid3_bmp::PlotBMP($BMPinfo); //exit; diff --git a/apps/media/getID3/getid3/module.graphic.png.php b/3rdparty/getid3/module.graphic.png.php similarity index 94% rename from apps/media/getID3/getid3/module.graphic.png.php rename to 3rdparty/getid3/module.graphic.png.php index b5b3d35941..9109c43689 100644 --- a/apps/media/getID3/getid3/module.graphic.png.php +++ b/3rdparty/getid3/module.graphic.png.php @@ -14,37 +14,38 @@ ///////////////////////////////////////////////////////////////// -class getid3_png +class getid3_png extends getid3_handler { - function getid3_png(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - // shortcut - $ThisFileInfo['png'] = array(); - $thisfile_png = &$ThisFileInfo['png']; + // shortcut + $info['png'] = array(); + $thisfile_png = &$info['png']; - $ThisFileInfo['fileformat'] = 'png'; - $ThisFileInfo['video']['dataformat'] = 'png'; - $ThisFileInfo['video']['lossless'] = false; + $info['fileformat'] = 'png'; + $info['video']['dataformat'] = 'png'; + $info['video']['lossless'] = false; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $PNGfiledata = fread($fd, GETID3_FREAD_BUFFER_SIZE); + 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") { - $ThisFileInfo['error'][] = 'First 8 bytes of file ('.getid3_lib::PrintHexBytes($PNGidentifier).') did not match expected PNG identifier'; - unset($ThisFileInfo['fileformat']); + $info['error'][] = 'First 8 bytes of file ('.getid3_lib::PrintHexBytes($PNGidentifier).') did not match expected PNG identifier'; + unset($info['fileformat']); return false; } - while (((ftell($fd) - (strlen($PNGfiledata) - $offset)) < $ThisFileInfo['filesize'])) { + 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($fd) < $ThisFileInfo['filesize'])) { - $PNGfiledata .= fread($fd, GETID3_FREAD_BUFFER_SIZE); + 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; @@ -80,10 +81,10 @@ class getid3_png $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); - $ThisFileInfo['video']['resolution_x'] = $thisfile_png_chunk_type_text['width']; - $ThisFileInfo['video']['resolution_y'] = $thisfile_png_chunk_type_text['height']; + $info['video']['resolution_x'] = $thisfile_png_chunk_type_text['width']; + $info['video']['resolution_y'] = $thisfile_png_chunk_type_text['height']; - $ThisFileInfo['video']['bits_per_sample'] = $this->IHDRcalculateBitsPerSample($thisfile_png_chunk_type_text['raw']['color_type'], $thisfile_png_chunk_type_text['raw']['bit_depth']); + $info['video']['bits_per_sample'] = $this->IHDRcalculateBitsPerSample($thisfile_png_chunk_type_text['raw']['color_type'], $thisfile_png_chunk_type_text['raw']['bit_depth']); break; @@ -123,10 +124,10 @@ class getid3_png case 4: case 6: - $ThisFileInfo['error'][] = 'Invalid color_type in tRNS chunk: '.$thisfile_png['IHDR']['raw']['color_type']; + $info['error'][] = 'Invalid color_type in tRNS chunk: '.$thisfile_png['IHDR']['raw']['color_type']; default: - $ThisFileInfo['warning'][] = 'Unhandled color_type in tRNS chunk: '.$thisfile_png['IHDR']['raw']['color_type']; + $info['warning'][] = 'Unhandled color_type in tRNS chunk: '.$thisfile_png['IHDR']['raw']['color_type']; break; } break; @@ -429,7 +430,7 @@ class getid3_png default: //unset($chunk['data']); $thisfile_png_chunk_type_text['header'] = $chunk; - $ThisFileInfo['warning'][] = 'Unhandled chunk type: '.$chunk['type_text']; + $info['warning'][] = 'Unhandled chunk type: '.$chunk['type_text']; break; } } diff --git a/3rdparty/getid3/module.graphic.svg.php b/3rdparty/getid3/module.graphic.svg.php new file mode 100644 index 0000000000..d9d52d74d0 --- /dev/null +++ b/3rdparty/getid3/module.graphic.svg.php @@ -0,0 +1,104 @@ + // +// 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 +{ + + + 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('#\]+)\>#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 tag'; + return false; + } + +} + + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/module.graphic.tiff.php b/3rdparty/getid3/module.graphic.tiff.php similarity index 60% rename from apps/media/getID3/getid3/module.graphic.tiff.php rename to 3rdparty/getid3/module.graphic.tiff.php index ae57cd6ad2..e977124216 100644 --- a/apps/media/getID3/getid3/module.graphic.tiff.php +++ b/3rdparty/getid3/module.graphic.tiff.php @@ -14,56 +14,57 @@ ///////////////////////////////////////////////////////////////// -class getid3_tiff +class getid3_tiff extends getid3_handler { - function getid3_tiff(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $TIFFheader = fread($fd, 4); + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $TIFFheader = fread($this->getid3->fp, 4); switch (substr($TIFFheader, 0, 2)) { case 'II': - $ThisFileInfo['tiff']['byte_order'] = 'Intel'; + $info['tiff']['byte_order'] = 'Intel'; break; case 'MM': - $ThisFileInfo['tiff']['byte_order'] = 'Motorola'; + $info['tiff']['byte_order'] = 'Motorola'; break; default: - $ThisFileInfo['error'][] = 'Invalid TIFF byte order identifier ('.substr($TIFFheader, 0, 2).') at offset '.$ThisFileInfo['avdataoffset']; + $info['error'][] = 'Invalid TIFF byte order identifier ('.substr($TIFFheader, 0, 2).') at offset '.$info['avdataoffset']; return false; break; } - $ThisFileInfo['fileformat'] = 'tiff'; - $ThisFileInfo['video']['dataformat'] = 'tiff'; - $ThisFileInfo['video']['lossless'] = true; - $ThisFileInfo['tiff']['ifd'] = array(); + $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($fd, 4), $ThisFileInfo['tiff']['byte_order']); + $nextIFDoffset = $this->TIFFendian2Int(fread($this->getid3->fp, 4), $info['tiff']['byte_order']); while ($nextIFDoffset > 0) { $CurrentIFD['offset'] = $nextIFDoffset; - fseek($fd, $ThisFileInfo['avdataoffset'] + $nextIFDoffset, SEEK_SET); - $CurrentIFD['fieldcount'] = $this->TIFFendian2Int(fread($fd, 2), $ThisFileInfo['tiff']['byte_order']); + 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($fd, 2), $ThisFileInfo['tiff']['byte_order']); - $CurrentIFD['fields'][$i]['raw']['type'] = $this->TIFFendian2Int(fread($fd, 2), $ThisFileInfo['tiff']['byte_order']); - $CurrentIFD['fields'][$i]['raw']['length'] = $this->TIFFendian2Int(fread($fd, 4), $ThisFileInfo['tiff']['byte_order']); - $CurrentIFD['fields'][$i]['raw']['offset'] = fread($fd, 4); + $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), $ThisFileInfo['tiff']['byte_order']); + $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'], $ThisFileInfo['tiff']['byte_order']); + $CurrentIFD['fields'][$i]['offset'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['offset'], $info['tiff']['byte_order']); } break; @@ -71,23 +72,23 @@ class getid3_tiff 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'], $ThisFileInfo['tiff']['byte_order']); + $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), $ThisFileInfo['tiff']['byte_order']); + $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'], $ThisFileInfo['tiff']['byte_order']); + $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'], $ThisFileInfo['tiff']['byte_order']); + $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'], $ThisFileInfo['tiff']['byte_order']); + $CurrentIFD['fields'][$i]['offset'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['offset'], $info['tiff']['byte_order']); } break; @@ -96,13 +97,13 @@ class getid3_tiff } } - $ThisFileInfo['tiff']['ifd'][] = $CurrentIFD; + $info['tiff']['ifd'][] = $CurrentIFD; $CurrentIFD = array(); - $nextIFDoffset = $this->TIFFendian2Int(fread($fd, 4), $ThisFileInfo['tiff']['byte_order']); + $nextIFDoffset = $this->TIFFendian2Int(fread($this->getid3->fp, 4), $info['tiff']['byte_order']); } - foreach ($ThisFileInfo['tiff']['ifd'] as $IFDid => $IFDarray) { + foreach ($info['tiff']['ifd'] as $IFDid => $IFDarray) { foreach ($IFDarray['fields'] as $key => $fieldarray) { switch ($fieldarray['raw']['tag']) { case 256: // ImageWidth @@ -110,8 +111,8 @@ class getid3_tiff case 258: // BitsPerSample case 259: // Compression if (!isset($fieldarray['value'])) { - fseek($fd, $fieldarray['offset'], SEEK_SET); - $ThisFileInfo['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'] = fread($fd, $fieldarray['raw']['length'] * $FieldTypeByteLength[$fieldarray['raw']['type']]); + 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; @@ -124,36 +125,36 @@ class getid3_tiff case 315: // Artist case 316: // HostComputer if (isset($fieldarray['value'])) { - $ThisFileInfo['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'] = $fieldarray['value']; + $info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'] = $fieldarray['value']; } else { - fseek($fd, $fieldarray['offset'], SEEK_SET); - $ThisFileInfo['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'] = fread($fd, $fieldarray['raw']['length'] * $FieldTypeByteLength[$fieldarray['raw']['type']]); + 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 - $ThisFileInfo['video']['resolution_x'] = $fieldarray['value']; + $info['video']['resolution_x'] = $fieldarray['value']; break; case 257: // ImageLength - $ThisFileInfo['video']['resolution_y'] = $fieldarray['value']; + $info['video']['resolution_y'] = $fieldarray['value']; break; case 258: // BitsPerSample if (isset($fieldarray['value'])) { - $ThisFileInfo['video']['bits_per_sample'] = $fieldarray['value']; + $info['video']['bits_per_sample'] = $fieldarray['value']; } else { - $ThisFileInfo['video']['bits_per_sample'] = 0; + $info['video']['bits_per_sample'] = 0; for ($i = 0; $i < $fieldarray['raw']['length']; $i++) { - $ThisFileInfo['video']['bits_per_sample'] += $this->TIFFendian2Int(substr($ThisFileInfo['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'], $i * $FieldTypeByteLength[$fieldarray['raw']['type']], $FieldTypeByteLength[$fieldarray['raw']['type']]), $ThisFileInfo['tiff']['byte_order']); + $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 - $ThisFileInfo['video']['codec'] = $this->TIFFcompressionMethod($fieldarray['value']); + $info['video']['codec'] = $this->TIFFcompressionMethod($fieldarray['value']); break; case 270: // ImageDescription @@ -163,7 +164,12 @@ class getid3_tiff case 306: // DateTime case 315: // Artist case 316: // HostComputer - @$ThisFileInfo['tiff']['comments'][$this->TIFFcommentName($fieldarray['raw']['tag'])][] = $ThisFileInfo['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data']; + $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: diff --git a/3rdparty/getid3/module.misc.cue.php b/3rdparty/getid3/module.misc.cue.php new file mode 100644 index 0000000000..c99ce24798 --- /dev/null +++ b/3rdparty/getid3/module.misc.cue.php @@ -0,0 +1,312 @@ + // +// 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 // +// Minor reformatting and similar small changes to integrate // +// into getID3 by James Heinrich // +// /// +///////////////////////////////////////////////////////////////// + +/* + * 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 +{ + var $cuesheet = array(); + + function Analyze() { + $info = &$this->getid3->info; + + $info['fileformat'] = 'cue'; + $this->readCueSheetFilename($info['filenamepath']); + $info['cue'] = $this->cuesheet; + return true; + } + + + + 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. + */ + 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. + */ + 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. + */ + 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.. + */ + 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. + */ + 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. + */ + 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. + */ + 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; + } + } + + 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. + */ + 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); + } + +} + +?> diff --git a/3rdparty/getid3/module.misc.exe.php b/3rdparty/getid3/module.misc.exe.php new file mode 100644 index 0000000000..108e62ecd7 --- /dev/null +++ b/3rdparty/getid3/module.misc.exe.php @@ -0,0 +1,61 @@ + // +// 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 +{ + + 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; + + } + +} + + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/module.misc.iso.php b/3rdparty/getid3/module.misc.iso.php similarity index 79% rename from apps/media/getID3/getid3/module.misc.iso.php rename to 3rdparty/getid3/module.misc.iso.php index 94df9294f4..727fdf8701 100644 --- a/apps/media/getID3/getid3/module.misc.iso.php +++ b/3rdparty/getid3/module.misc.iso.php @@ -14,25 +14,27 @@ ///////////////////////////////////////////////////////////////// -class getid3_iso +class getid3_iso extends getid3_handler { - function getid3_iso($fd, &$ThisFileInfo) { - $ThisFileInfo['fileformat'] = 'iso'; + function Analyze() { + $info = &$this->getid3->info; + + $info['fileformat'] = 'iso'; for ($i = 16; $i <= 19; $i++) { - fseek($fd, 2048 * $i, SEEK_SET); - $ISOheader = fread($fd, 2048); + 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: - $ThisFileInfo['iso']['primary_volume_descriptor']['offset'] = 2048 * $i; - $this->ParsePrimaryVolumeDescriptor($ISOheader, $ThisFileInfo); + $info['iso']['primary_volume_descriptor']['offset'] = 2048 * $i; + $this->ParsePrimaryVolumeDescriptor($ISOheader); break; case 2: - $ThisFileInfo['iso']['supplementary_volume_descriptor']['offset'] = 2048 * $i; - $this->ParseSupplementaryVolumeDescriptor($ISOheader, $ThisFileInfo); + $info['iso']['supplementary_volume_descriptor']['offset'] = 2048 * $i; + $this->ParseSupplementaryVolumeDescriptor($ISOheader); break; default: @@ -42,35 +44,33 @@ class getid3_iso } } - $this->ParsePathTable($fd, $ThisFileInfo); - - $ThisFileInfo['iso']['files'] = array(); - foreach ($ThisFileInfo['iso']['path_table']['directories'] as $directorynum => $directorydata) { - - $ThisFileInfo['iso']['directories'][$directorynum] = $this->ParseDirectoryRecord($fd, $directorydata, $ThisFileInfo); + $this->ParsePathTable(); + $info['iso']['files'] = array(); + foreach ($info['iso']['path_table']['directories'] as $directorynum => $directorydata) { + $info['iso']['directories'][$directorynum] = $this->ParseDirectoryRecord($directorydata); } return true; - } - function ParsePrimaryVolumeDescriptor(&$ISOheader, &$ThisFileInfo) { + 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 - $ThisFileInfo['iso']['primary_volume_descriptor']['raw'] = array(); - $thisfile_iso_primaryVD = &$ThisFileInfo['iso']['primary_volume_descriptor']; + $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') { - $ThisFileInfo['error'][] = 'Expected "CD001" at offset ('.($thisfile_iso_primaryVD['offset'] + 1).'), found "'.$thisfile_iso_primaryVD_raw['standard_identifier'].'" instead'; - unset($ThisFileInfo['fileformat']); - unset($ThisFileInfo['iso']); + $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; } @@ -121,29 +121,30 @@ class getid3_iso $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) > $ThisFileInfo['filesize']) { - $ThisFileInfo['error'][] = 'Volume Space Size ('.($thisfile_iso_primaryVD_raw['volume_space_size'] * 2048).' bytes) is larger than the file size ('.$ThisFileInfo['filesize'].' bytes) (truncated file?)'; + 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; } - function ParseSupplementaryVolumeDescriptor(&$ISOheader, &$ThisFileInfo) { + 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 - $ThisFileInfo['iso']['supplementary_volume_descriptor']['raw'] = array(); - $thisfile_iso_supplementaryVD = &$ThisFileInfo['iso']['supplementary_volume_descriptor']; + $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') { - $ThisFileInfo['error'][] = 'Expected "CD001" at offset ('.($thisfile_iso_supplementaryVD['offset'] + 1).'), found "'.$thisfile_iso_supplementaryVD_raw['standard_identifier'].'" instead'; - unset($ThisFileInfo['fileformat']); - unset($ThisFileInfo['iso']); + $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; } @@ -199,62 +200,63 @@ class getid3_iso $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']) > $ThisFileInfo['filesize']) { - $ThisFileInfo['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 ('.$ThisFileInfo['filesize'].' bytes) (truncated file?)'; + 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; } - function ParsePathTable($fd, &$ThisFileInfo) { - if (!isset($ThisFileInfo['iso']['supplementary_volume_descriptor']['raw']['path_table_l_location']) && !isset($ThisFileInfo['iso']['primary_volume_descriptor']['raw']['path_table_l_location'])) { + 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($ThisFileInfo['iso']['supplementary_volume_descriptor']['raw']['path_table_l_location'])) { - $PathTableLocation = $ThisFileInfo['iso']['supplementary_volume_descriptor']['raw']['path_table_l_location']; - $PathTableSize = $ThisFileInfo['iso']['supplementary_volume_descriptor']['raw']['path_table_size']; + 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 = $ThisFileInfo['iso']['primary_volume_descriptor']['raw']['path_table_l_location']; - $PathTableSize = $ThisFileInfo['iso']['primary_volume_descriptor']['raw']['path_table_size']; + $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) > $ThisFileInfo['filesize']) { - $ThisFileInfo['error'][] = 'Path Table Location specifies an offset ('.($PathTableLocation * 2048).') beyond the end-of-file ('.$ThisFileInfo['filesize'].')'; + if (($PathTableLocation * 2048) > $info['filesize']) { + $info['error'][] = 'Path Table Location specifies an offset ('.($PathTableLocation * 2048).') beyond the end-of-file ('.$info['filesize'].')'; return false; } - $ThisFileInfo['iso']['path_table']['offset'] = $PathTableLocation * 2048; - fseek($fd, $ThisFileInfo['iso']['path_table']['offset'], SEEK_SET); - $ThisFileInfo['iso']['path_table']['raw'] = fread($fd, $PathTableSize); + $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 - $ThisFileInfo['iso']['path_table']['directories'][$pathcounter] = array(); - $thisfile_iso_pathtable_directories_current = &$ThisFileInfo['iso']['path_table']['directories'][$pathcounter]; + $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($ThisFileInfo['iso']['path_table']['raw'], $offset, 1)); + $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($ThisFileInfo['iso']['path_table']['raw'], $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($ThisFileInfo['iso']['path_table']['raw'], $offset, 4)); + $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($ThisFileInfo['iso']['path_table']['raw'], $offset, 2)); + $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($ThisFileInfo['iso']['path_table']['raw'], $offset, $thisfile_iso_pathtable_directories_current['length']); + $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, $ThisFileInfo['encoding'], $thisfile_iso_pathtable_directories_current['name']); + $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'] = $ThisFileInfo['iso']['path_table']['directories'][$thisfile_iso_pathtable_directories_current['parent_directory']]['full_path'].$thisfile_iso_pathtable_directories_current['name_ascii'].'/'; + $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']; @@ -265,19 +267,20 @@ class getid3_iso } - function ParseDirectoryRecord(&$fd, $directorydata, &$ThisFileInfo) { - if (isset($ThisFileInfo['iso']['supplementary_volume_descriptor'])) { + 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($fd, $directorydata['location_bytes'], SEEK_SET); - $DirectoryRecordData = fread($fd, 1); + fseek($this->getid3->fp, $directorydata['location_bytes'], SEEK_SET); + $DirectoryRecordData = fread($this->getid3->fp, 1); while (ord($DirectoryRecordData{0}) > 33) { - $DirectoryRecordData .= fread($fd, ord($DirectoryRecordData{0}) - 1); + $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)); @@ -291,7 +294,7 @@ class getid3_iso $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, $ThisFileInfo['encoding'], $ThisDirectoryRecord['raw']['file_identifier']); + $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; @@ -307,11 +310,11 @@ class getid3_iso $ThisDirectoryRecord['filename'] = $directorydata['full_path']; } else { $ThisDirectoryRecord['filename'] = $directorydata['full_path'].$this->ISOstripFilenameVersion($ThisDirectoryRecord['file_identifier_ascii']); - $ThisFileInfo['iso']['files'] = getid3_lib::array_merge_clobber($ThisFileInfo['iso']['files'], getid3_lib::CreateDeepArray($ThisDirectoryRecord['filename'], '/', $ThisDirectoryRecord['filesize'])); + $info['iso']['files'] = getid3_lib::array_merge_clobber($info['iso']['files'], getid3_lib::CreateDeepArray($ThisDirectoryRecord['filename'], '/', $ThisDirectoryRecord['filesize'])); } $DirectoryRecord[] = $ThisDirectoryRecord; - $DirectoryRecordData = fread($fd, 1); + $DirectoryRecordData = fread($this->getid3->fp, 1); } return $DirectoryRecord; diff --git a/apps/media/getID3/getid3/module.misc.msoffice.php b/3rdparty/getid3/module.misc.msoffice.php similarity index 56% rename from apps/media/getID3/getid3/module.misc.msoffice.php rename to 3rdparty/getid3/module.misc.msoffice.php index cb65abf223..8fea108534 100644 --- a/apps/media/getID3/getid3/module.misc.msoffice.php +++ b/3rdparty/getid3/module.misc.msoffice.php @@ -14,15 +14,23 @@ ///////////////////////////////////////////////////////////////// -class getid3_doc +class getid3_msoffice extends getid3_handler { - function getid3_doc(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - $ThisFileInfo['fileformat'] = 'doc'; + 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'; - $ThisFileInfo['error'][] = 'MS Office (.doc, .xls, etc) parsing not enabled in this version of getID3()'; - return false; +$info['error'][] = 'MS Office (.doc, .xls, etc) parsing not enabled in this version of getID3() ['.$this->getid3->version().']'; +return false; } diff --git a/apps/media/getID3/getid3/module.misc.par2.php b/3rdparty/getid3/module.misc.par2.php similarity index 81% rename from apps/media/getID3/getid3/module.misc.par2.php rename to 3rdparty/getid3/module.misc.par2.php index 9f0e22180f..d8b0a388b7 100644 --- a/apps/media/getID3/getid3/module.misc.par2.php +++ b/3rdparty/getid3/module.misc.par2.php @@ -14,14 +14,15 @@ ///////////////////////////////////////////////////////////////// -class getid3_par2 +class getid3_par2 extends getid3_handler { - function getid3_par2(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - $ThisFileInfo['fileformat'] = 'par2'; + $info['fileformat'] = 'par2'; - $ThisFileInfo['error'][] = 'PAR2 parsing not enabled in this version of getID3()'; + $info['error'][] = 'PAR2 parsing not enabled in this version of getID3()'; return false; } diff --git a/apps/media/getID3/getid3/module.misc.pdf.php b/3rdparty/getid3/module.misc.pdf.php similarity index 79% rename from apps/media/getID3/getid3/module.misc.pdf.php rename to 3rdparty/getid3/module.misc.pdf.php index c6e3294b69..38ed646a9e 100644 --- a/apps/media/getID3/getid3/module.misc.pdf.php +++ b/3rdparty/getid3/module.misc.pdf.php @@ -14,14 +14,15 @@ ///////////////////////////////////////////////////////////////// -class getid3_pdf +class getid3_pdf extends getid3_handler { - function getid3_pdf(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - $ThisFileInfo['fileformat'] = 'pdf'; + $info['fileformat'] = 'pdf'; - $ThisFileInfo['error'][] = 'PDF parsing not enabled in this version of getID3()'; + $info['error'][] = 'PDF parsing not enabled in this version of getID3() ['.$this->getid3->version().']'; return false; } diff --git a/apps/media/getID3/getid3/module.tag.apetag.php b/3rdparty/getid3/module.tag.apetag.php similarity index 54% rename from apps/media/getID3/getid3/module.tag.apetag.php rename to 3rdparty/getid3/module.tag.apetag.php index 2b67e14b4a..6522475f51 100644 --- a/apps/media/getID3/getid3/module.tag.apetag.php +++ b/3rdparty/getid3/module.tag.apetag.php @@ -13,13 +13,16 @@ // /// ///////////////////////////////////////////////////////////////// -class getid3_apetag +class getid3_apetag extends getid3_handler { + var $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 + var $overrideendoffset = 0; - function getid3_apetag(&$fd, &$ThisFileInfo, $overrideendoffset=0) { + function Analyze() { + $info = &$this->getid3->info; - if ($ThisFileInfo['filesize'] >= pow(2, 31)) { - $ThisFileInfo['warning'][] = 'Unable to check for APEtags because file is larger than 2GB'; + 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; } @@ -27,69 +30,69 @@ class getid3_apetag $apetagheadersize = 32; $lyrics3tagsize = 10; - if ($overrideendoffset == 0) { + if ($this->overrideendoffset == 0) { - fseek($fd, 0 - $id3v1tagsize - $apetagheadersize - $lyrics3tagsize, SEEK_END); - $APEfooterID3v1 = fread($fd, $id3v1tagsize + $apetagheadersize + $lyrics3tagsize); + 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 - $ThisFileInfo['ape']['tag_offset_end'] = $ThisFileInfo['filesize'] - $id3v1tagsize; + $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 - $ThisFileInfo['ape']['tag_offset_end'] = $ThisFileInfo['filesize']; + $info['ape']['tag_offset_end'] = $info['filesize']; } } else { - fseek($fd, $overrideendoffset - $apetagheadersize, SEEK_SET); - if (fread($fd, 8) == 'APETAGEX') { - $ThisFileInfo['ape']['tag_offset_end'] = $overrideendoffset; + 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($ThisFileInfo['ape']['tag_offset_end'])) { + if (!isset($info['ape']['tag_offset_end'])) { // APE tag not found - unset($ThisFileInfo['ape']); + unset($info['ape']); return false; } // shortcut - $thisfile_ape = &$ThisFileInfo['ape']; + $thisfile_ape = &$info['ape']; - fseek($fd, $thisfile_ape['tag_offset_end'] - $apetagheadersize, SEEK_SET); - $APEfooterData = fread($fd, 32); + 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))) { - $ThisFileInfo['error'][] = 'Error parsing APE footer at offset '.$thisfile_ape['tag_offset_end']; + $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($fd, $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'] - $apetagheadersize, SEEK_SET); - $thisfile_ape['tag_offset_start'] = ftell($fd); - $APEtagData = fread($fd, $thisfile_ape['footer']['raw']['tagsize'] + $apetagheadersize); + 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($fd, $thisfile_ape['tag_offset_start'], SEEK_SET); - $APEtagData = fread($fd, $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']); } - $ThisFileInfo['avdataend'] = $thisfile_ape['tag_offset_start']; + $info['avdataend'] = $thisfile_ape['tag_offset_start']; - if (isset($ThisFileInfo['id3v1']['tag_offset_start']) && ($ThisFileInfo['id3v1']['tag_offset_start'] < $thisfile_ape['tag_offset_end'])) { - $ThisFileInfo['warning'][] = 'ID3v1 tag information ignored since it appears to be a false synch in APEtag data'; - unset($ThisFileInfo['id3v1']); - foreach ($ThisFileInfo['warning'] as $key => $value) { + 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($ThisFileInfo['warning'][$key]); - sort($ThisFileInfo['warning']); + unset($info['warning'][$key]); + sort($info['warning']); break; } } @@ -100,14 +103,14 @@ class getid3_apetag if ($thisfile_ape['header'] = $this->parseAPEheaderFooter(substr($APEtagData, 0, $apetagheadersize))) { $offset += $apetagheadersize; } else { - $ThisFileInfo['error'][] = 'Error parsing APE header at offset '.$thisfile_ape['tag_offset_start']; + $info['error'][] = 'Error parsing APE header at offset '.$thisfile_ape['tag_offset_start']; return false; } } // shortcut - $ThisFileInfo['replay_gain'] = array(); - $thisfile_replaygain = &$ThisFileInfo['replay_gain']; + $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)); @@ -115,7 +118,7 @@ class getid3_apetag $item_flags = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4)); $offset += 4; if (strstr(substr($APEtagData, $offset), "\x00") === false) { - $ThisFileInfo['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); + $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; @@ -125,6 +128,8 @@ class getid3_apetag $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; @@ -150,7 +155,7 @@ class getid3_apetag $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) { - $ThisFileInfo['warning'][] = 'ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")'; + $info['warning'][] = 'ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")'; } break; @@ -163,7 +168,7 @@ class getid3_apetag $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) { - $ThisFileInfo['warning'][] = 'ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")'; + $info['warning'][] = 'ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")'; } break; @@ -187,23 +192,100 @@ class getid3_apetag break; case 'tracknumber': - foreach ($thisfile_ape_items_current['data'] as $comment) { - $thisfile_ape['comments']['track'][] = $comment; + 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: - foreach ($thisfile_ape_items_current['data'] as $comment) { - $thisfile_ape['comments'][strtolower($item_key)][] = $comment; + 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($ThisFileInfo['replay_gain']); + unset($info['replay_gain']); } - return true; } diff --git a/apps/media/getID3/getid3/module.tag.id3v1.php b/3rdparty/getid3/module.tag.id3v1.php similarity index 86% rename from apps/media/getID3/getid3/module.tag.id3v1.php rename to 3rdparty/getid3/module.tag.id3v1.php index 6fd2dd84a7..a9932d13f3 100644 --- a/apps/media/getID3/getid3/module.tag.id3v1.php +++ b/3rdparty/getid3/module.tag.id3v1.php @@ -14,23 +14,24 @@ ///////////////////////////////////////////////////////////////// -class getid3_id3v1 +class getid3_id3v1 extends getid3_handler { - function getid3_id3v1(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - if ($ThisFileInfo['filesize'] >= pow(2, 31)) { - $ThisFileInfo['warning'][] = 'Unable to check for ID3v1 because file is larger than 2GB'; + 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($fd, -256, SEEK_END); - $preid3v1 = fread($fd, 128); - $id3v1tag = fread($fd, 128); + 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') { - $ThisFileInfo['avdataend'] = $ThisFileInfo['filesize'] - 128; + $info['avdataend'] = $info['filesize'] - 128; $ParsedID3v1['title'] = $this->cutfield(substr($id3v1tag, 3, 30)); $ParsedID3v1['artist'] = $this->cutfield(substr($id3v1tag, 33, 30)); @@ -51,7 +52,7 @@ class getid3_id3v1 if (!empty($ParsedID3v1['genre'])) { unset($ParsedID3v1['genreid']); } - if (empty($ParsedID3v1['genre']) || (@$ParsedID3v1['genre'] == 'Unknown')) { + if (isset($ParsedID3v1['genre']) && (empty($ParsedID3v1['genre']) || ($ParsedID3v1['genre'] == 'Unknown'))) { unset($ParsedID3v1['genre']); } @@ -67,17 +68,17 @@ class getid3_id3v1 $ParsedID3v1['year'], (isset($ParsedID3v1['genre']) ? $this->LookupGenreID($ParsedID3v1['genre']) : false), $ParsedID3v1['comment'], - @$ParsedID3v1['track']); + (!empty($ParsedID3v1['track']) ? $ParsedID3v1['track'] : '')); $ParsedID3v1['padding_valid'] = true; if ($id3v1tag !== $GoodFormatID3v1tag) { $ParsedID3v1['padding_valid'] = false; - $ThisFileInfo['warning'][] = 'Some ID3v1 fields do not use NULL characters for padding'; + $info['warning'][] = 'Some ID3v1 fields do not use NULL characters for padding'; } - $ParsedID3v1['tag_offset_end'] = $ThisFileInfo['filesize']; + $ParsedID3v1['tag_offset_end'] = $info['filesize']; $ParsedID3v1['tag_offset_start'] = $ParsedID3v1['tag_offset_end'] - 128; - $ThisFileInfo['id3v1'] = $ParsedID3v1; + $info['id3v1'] = $ParsedID3v1; } if (substr($preid3v1, 0, 3) == 'TAG') { @@ -93,19 +94,19 @@ class getid3_id3v1 // a Lyrics3 tag footer was found before the last ID3v1, assume false "TAG" synch } else { // APE and Lyrics3 footers not found - assume double ID3v1 - $ThisFileInfo['warning'][] = 'Duplicate ID3v1 tag detected - this has been known to happen with iTunes'; - $ThisFileInfo['avdataend'] -= 128; + $info['warning'][] = 'Duplicate ID3v1 tag detected - this has been known to happen with iTunes'; + $info['avdataend'] -= 128; } } return true; } - function cutfield($str) { + static function cutfield($str) { return trim(substr($str, 0, strcspn($str, "\x00"))); } - function ArrayOfGenres($allowSCMPXextended=false) { + static function ArrayOfGenres($allowSCMPXextended=false) { static $GenreLookup = array( 0 => 'Blues', 1 => 'Classic Rock', @@ -251,7 +252,7 @@ class getid3_id3v1 141 => 'Christian Rock', 142 => 'Merengue', 143 => 'Salsa', - 144 => 'Trash Metal', + 144 => 'Thrash Metal', 145 => 'Anime', 146 => 'JPop', 147 => 'Synthpop', @@ -289,12 +290,15 @@ class getid3_id3v1 return ($allowSCMPXextended ? $GenreLookupSCMPX : $GenreLookup); } - function LookupGenreName($genreid, $allowSCMPXextended=true) { + 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; } @@ -302,28 +306,25 @@ class getid3_id3v1 return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false); } - function LookupGenreID($genre, $allowSCMPXextended=false) { + static function LookupGenreID($genre, $allowSCMPXextended=false) { $GenreLookup = getid3_id3v1::ArrayOfGenres($allowSCMPXextended); $LowerCaseNoSpaceSearchTerm = strtolower(str_replace(' ', '', $genre)); foreach ($GenreLookup as $key => $value) { - foreach ($GenreLookup as $key => $value) { - if (strtolower(str_replace(' ', '', $value)) == $LowerCaseNoSpaceSearchTerm) { - return $key; - } + if (strtolower(str_replace(' ', '', $value)) == $LowerCaseNoSpaceSearchTerm) { + return $key; } - return false; } - return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false); + return false; } - function StandardiseID3v1GenreName($OriginalGenre) { + static function StandardiseID3v1GenreName($OriginalGenre) { if (($GenreID = getid3_id3v1::LookupGenreID($OriginalGenre)) !== false) { return getid3_id3v1::LookupGenreName($GenreID); } return $OriginalGenre; } - function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') { + 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); diff --git a/apps/media/getID3/getid3/module.tag.id3v2.php b/3rdparty/getid3/module.tag.id3v2.php similarity index 79% rename from apps/media/getID3/getid3/module.tag.id3v2.php rename to 3rdparty/getid3/module.tag.id3v2.php index 701d72800c..56adeb9506 100644 --- a/apps/media/getID3/getid3/module.tag.id3v2.php +++ b/3rdparty/getid3/module.tag.id3v2.php @@ -15,10 +15,14 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true); -class getid3_id3v2 +class getid3_id3v2 extends getid3_handler { + var $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 + var $StartingOffset = 0; + + function Analyze() { + $info = &$this->getid3->info; - function getid3_id3v2(&$fd, &$ThisFileInfo, $StartingOffset=0) { // Overall tag structure: // +-----------------------------+ // | Header (10 bytes) | @@ -42,14 +46,14 @@ class getid3_id3v2 // shortcuts - $ThisFileInfo['id3v2']['header'] = true; - $thisfile_id3v2 = &$ThisFileInfo['id3v2']; + $info['id3v2']['header'] = true; + $thisfile_id3v2 = &$info['id3v2']; $thisfile_id3v2['flags'] = array(); $thisfile_id3v2_flags = &$thisfile_id3v2['flags']; - fseek($fd, $StartingOffset, SEEK_SET); - $header = fread($fd, 10); + fseek($this->getid3->fp, $this->StartingOffset, SEEK_SET); + $header = fread($this->getid3->fp, 10); if (substr($header, 0, 3) == 'ID3' && strlen($header) == 10) { $thisfile_id3v2['majorversion'] = ord($header{3}); @@ -60,14 +64,14 @@ class getid3_id3v2 } else { - unset($ThisFileInfo['id3v2']); + unset($info['id3v2']); return false; } if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists) - $ThisFileInfo['error'][] = 'this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion']; + $info['error'][] = 'this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion']; return false; } @@ -98,7 +102,7 @@ class getid3_id3v2 $thisfile_id3v2['headerlength'] = getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length - $thisfile_id3v2['tag_offset_start'] = $StartingOffset; + $thisfile_id3v2['tag_offset_start'] = $this->StartingOffset; $thisfile_id3v2['tag_offset_end'] = $thisfile_id3v2['tag_offset_start'] + $thisfile_id3v2['headerlength']; @@ -120,18 +124,18 @@ class getid3_id3v2 // Flags $xx xx $sizeofframes = $thisfile_id3v2['headerlength'] - 10; // not including 10-byte initial header - if (@$thisfile_id3v2['exthead']['length']) { + if (!empty($thisfile_id3v2['exthead']['length'])) { $sizeofframes -= ($thisfile_id3v2['exthead']['length'] + 4); } - if (@$thisfile_id3v2_flags['isfooter']) { + if (!empty($thisfile_id3v2_flags['isfooter'])) { $sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio } if ($sizeofframes > 0) { - $framedata = fread($fd, $sizeofframes); // read all frames from file into $framedata variable + $framedata = fread($this->getid3->fp, $sizeofframes); // read all frames from file into $framedata variable // if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x) - if (@$thisfile_id3v2_flags['unsynch'] && ($id3v2_majorversion <= 3)) { + if (!empty($thisfile_id3v2_flags['unsynch']) && ($id3v2_majorversion <= 3)) { $framedata = $this->DeUnsynchronise($framedata); } // [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead @@ -141,12 +145,12 @@ class getid3_id3v2 // the frame header [S:4.1.2] indicates unsynchronisation. - //$framedataoffset = 10 + (@$thisfile_id3v2['exthead']['length'] ? $thisfile_id3v2['exthead']['length'] + 4 : 0); // how many bytes into the stream - start from after the 10-byte header (and extended header length+4, if present) + //$framedataoffset = 10 + ($thisfile_id3v2['exthead']['length'] ? $thisfile_id3v2['exthead']['length'] + 4 : 0); // how many bytes into the stream - start from after the 10-byte header (and extended header length+4, if present) $framedataoffset = 10; // how many bytes into the stream - start from after the 10-byte header // Extended Header - if (@$thisfile_id3v2_flags['exthead']) { + if (!empty($thisfile_id3v2_flags['exthead'])) { $extended_header_offset = 0; if ($id3v2_majorversion == 3) { @@ -191,33 +195,56 @@ class getid3_id3v2 // d - Tag restrictions // Flag data length $01 - $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), 1); + $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), true); $extended_header_offset += 4; - $thisfile_id3v2['exthead']['flag_bytes'] = 1; + $thisfile_id3v2['exthead']['flag_bytes'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should always be 1 + $extended_header_offset += 1; + $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes'])); $extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes']; - $thisfile_id3v2['exthead']['flags']['update'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x4000); - $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x2000); - $thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x1000); + $thisfile_id3v2['exthead']['flags']['update'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x40); + $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x20); + $thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x10); + + if ($thisfile_id3v2['exthead']['flags']['update']) { + $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 0 + $extended_header_offset += 1; + } if ($thisfile_id3v2['exthead']['flags']['crc']) { - $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 5), 1); - $extended_header_offset += 5; + $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 5 + $extended_header_offset += 1; + $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $ext_header_chunk_length), true, false); + $extended_header_offset += $ext_header_chunk_length; } + if ($thisfile_id3v2['exthead']['flags']['restrictions']) { + $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 1 + $extended_header_offset += 1; + // %ppqrrstt $restrictions_raw = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); $extended_header_offset += 1; - $thisfile_id3v2['exthead']['flags']['restrictions']['tagsize'] = ($restrictions_raw && 0xC0) >> 6; // p - Tag size restrictions - $thisfile_id3v2['exthead']['flags']['restrictions']['textenc'] = ($restrictions_raw && 0x20) >> 5; // q - Text encoding restrictions - $thisfile_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw && 0x18) >> 3; // r - Text fields size restrictions - $thisfile_id3v2['exthead']['flags']['restrictions']['imgenc'] = ($restrictions_raw && 0x04) >> 2; // s - Image encoding restrictions - $thisfile_id3v2['exthead']['flags']['restrictions']['imgsize'] = ($restrictions_raw && 0x03) >> 0; // t - Image size restrictions + $thisfile_id3v2['exthead']['flags']['restrictions']['tagsize'] = ($restrictions_raw & 0xC0) >> 6; // p - Tag size restrictions + $thisfile_id3v2['exthead']['flags']['restrictions']['textenc'] = ($restrictions_raw & 0x20) >> 5; // q - Text encoding restrictions + $thisfile_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw & 0x18) >> 3; // r - Text fields size restrictions + $thisfile_id3v2['exthead']['flags']['restrictions']['imgenc'] = ($restrictions_raw & 0x04) >> 2; // s - Image encoding restrictions + $thisfile_id3v2['exthead']['flags']['restrictions']['imgsize'] = ($restrictions_raw & 0x03) >> 0; // t - Image size restrictions + + $thisfile_id3v2['exthead']['flags']['restrictions_text']['tagsize'] = $this->LookupExtendedHeaderRestrictionsTagSizeLimits($thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']); + $thisfile_id3v2['exthead']['flags']['restrictions_text']['textenc'] = $this->LookupExtendedHeaderRestrictionsTextEncodings($thisfile_id3v2['exthead']['flags']['restrictions']['textenc']); + $thisfile_id3v2['exthead']['flags']['restrictions_text']['textsize'] = $this->LookupExtendedHeaderRestrictionsTextFieldSize($thisfile_id3v2['exthead']['flags']['restrictions']['textsize']); + $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgenc'] = $this->LookupExtendedHeaderRestrictionsImageEncoding($thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']); + $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgsize'] = $this->LookupExtendedHeaderRestrictionsImageSizeSize($thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']); } + if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) { + $info['warning'][] = 'ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')'; + } } + $framedataoffset += $extended_header_offset; $framedata = substr($framedata, $extended_header_offset); } // end extended header @@ -233,7 +260,7 @@ class getid3_id3v2 if ($framedata{$i} != "\x00") { $thisfile_id3v2['padding']['valid'] = false; $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i; - $ThisFileInfo['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)'; + $info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)'; break; } } @@ -273,7 +300,7 @@ class getid3_id3v2 } elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) { // MP3ext known broken frames - "ok" for the purposes of this test } elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) { - $ThisFileInfo['warning'][] = 'ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3'; + $info['warning'][] = 'ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3'; $id3v2_majorversion = 3; $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer } @@ -295,7 +322,7 @@ class getid3_id3v2 if ($framedata{$i} != "\x00") { $thisfile_id3v2['padding']['valid'] = false; $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i; - $ThisFileInfo['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)'; + $info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)'; break; } } @@ -303,7 +330,7 @@ class getid3_id3v2 } if ($frame_name == 'COM ') { - $ThisFileInfo['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably others too)]'; + $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably others too)]'; $frame_name = 'COMM'; } if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) { @@ -315,7 +342,7 @@ class getid3_id3v2 $parsedFrame['datalength'] = getid3_lib::CastAsInt($frame_size); $parsedFrame['dataoffset'] = $framedataoffset; - $this->ParseID3v2Frame($parsedFrame, $ThisFileInfo); + $this->ParseID3v2Frame($parsedFrame); $thisfile_id3v2[$frame_name][] = $parsedFrame; $framedata = substr($framedata, $frame_size); @@ -328,28 +355,28 @@ class getid3_id3v2 // next frame is valid, just skip the current frame $framedata = substr($framedata, $frame_size); - $ThisFileInfo['warning'][] = 'Next ID3v2 frame is valid, skipping current frame.'; + $info['warning'][] = 'Next ID3v2 frame is valid, skipping current frame.'; } else { // next frame is invalid too, abort processing //unset($framedata); $framedata = null; - $ThisFileInfo['error'][] = 'Next ID3v2 frame is also invalid, aborting processing.'; + $info['error'][] = 'Next ID3v2 frame is also invalid, aborting processing.'; } } elseif ($frame_size == strlen($framedata)) { // this is the last frame, just skip - $ThisFileInfo['warning'][] = 'This was the last ID3v2 frame.'; + $info['warning'][] = 'This was the last ID3v2 frame.'; } else { // next frame is invalid too, abort processing //unset($framedata); $framedata = null; - $ThisFileInfo['warning'][] = 'Invalid ID3v2 frame size, aborting.'; + $info['warning'][] = 'Invalid ID3v2 frame size, aborting.'; } if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) { @@ -362,21 +389,21 @@ class getid3_id3v2 case "\x00".'MP': case ' MP': case 'MP3': - $ThisFileInfo['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]'; + $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]'; break; default: - $ThisFileInfo['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).'; + $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).'; break; } - } elseif ($frame_size > strlen(@$framedata)){ + } elseif (!isset($framedata) || ($frame_size > strlen($framedata))) { - $ThisFileInfo['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($framedata) ('.strlen($framedata).')).'; + $info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($framedata) ('.(isset($framedata) ? strlen($framedata) : 'null').')).'; } else { - $ThisFileInfo['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).'; + $info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).'; } @@ -397,7 +424,7 @@ class getid3_id3v2 // ID3v2 size 4 * %0xxxxxxx if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) { - $footer = fread($fd, 10); + $footer = fread($this->getid3->fp, 10); if (substr($footer, 0, 3) == '3DI') { $thisfile_id3v2['footer'] = true; $thisfile_id3v2['majorversion_footer'] = ord($footer{3}); @@ -417,7 +444,7 @@ class getid3_id3v2 if (isset($thisfile_id3v2['comments']['genre'])) { foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) { unset($thisfile_id3v2['comments']['genre'][$key]); - $thisfile_id3v2['comments'] = getid3_lib::array_merge_noclobber($thisfile_id3v2['comments'], $this->ParseID3v2GenreString($value)); + $thisfile_id3v2['comments'] = getid3_lib::array_merge_noclobber($thisfile_id3v2['comments'], array('genre'=>$this->ParseID3v2GenreString($value))); } } @@ -429,15 +456,39 @@ class getid3_id3v2 } } - if (!isset($thisfile_id3v2['comments']['year']) && ereg('^([0-9]{4})', trim(@$thisfile_id3v2['comments']['recording_time'][0]), $matches)) { + if (!isset($thisfile_id3v2['comments']['year']) && !empty($thisfile_id3v2['comments']['recording_time'][0]) && preg_match('#^([0-9]{4})#', trim($thisfile_id3v2['comments']['recording_time'][0]), $matches)) { $thisfile_id3v2['comments']['year'] = array($matches[1]); } + if (!empty($thisfile_id3v2['TXXX'])) { + // MediaMonkey does this, maybe others: write a blank RGAD frame, but put replay-gain adjustment values in TXXX frames + foreach ($thisfile_id3v2['TXXX'] as $txxx_array) { + switch ($txxx_array['description']) { + case 'replaygain_track_gain': + if (empty($info['replay_gain']['track']['adjustment']) && !empty($txxx_array['data'])) { + $info['replay_gain']['track']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data']))); + } + break; + case 'replaygain_track_peak': + if (empty($info['replay_gain']['track']['peak']) && !empty($txxx_array['data'])) { + $info['replay_gain']['track']['peak'] = floatval($txxx_array['data']); + } + break; + case 'replaygain_album_gain': + if (empty($info['replay_gain']['album']['adjustment']) && !empty($txxx_array['data'])) { + $info['replay_gain']['album']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data']))); + } + break; + } + } + } + + // Set avdataoffset - $ThisFileInfo['avdataoffset'] = $thisfile_id3v2['headerlength']; + $info['avdataoffset'] = $thisfile_id3v2['headerlength']; if (isset($thisfile_id3v2['footer'])) { - $ThisFileInfo['avdataoffset'] += 10; + $info['avdataoffset'] += 10; } return true; @@ -448,68 +499,30 @@ class getid3_id3v2 // Parse genres into arrays of genreName and genreID // ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)' // ID3v2.4.x: '21' $00 'Eurodisco' $00 - - $genrestring = trim($genrestring); - $returnarray = array(); - if (strpos($genrestring, "\x00") !== false) { - $unprocessed = trim($genrestring); // trailing nulls will cause an infinite loop. - $genrestring = ''; - while (strpos($unprocessed, "\x00") !== false) { - // convert null-seperated v2.4-format into v2.3 ()-seperated format - $endpos = strpos($unprocessed, "\x00"); - $genrestring .= '('.substr($unprocessed, 0, $endpos).')'; - $unprocessed = substr($unprocessed, $endpos + 1); - } - unset($unprocessed); - } elseif (eregi('^([0-9]+|CR|RX)$', $genrestring)) { - // some tagging program (including some that use TagLib) fail to include null byte after numeric genre - $genrestring = '('.$genrestring.')'; - } - if (getid3_id3v1::LookupGenreID($genrestring)) { - - $returnarray['genre'][] = $genrestring; - - } else { - - while (strpos($genrestring, '(') !== false) { - - $startpos = strpos($genrestring, '('); - $endpos = strpos($genrestring, ')'); - if (substr($genrestring, $startpos + 1, 1) == '(') { - $genrestring = substr($genrestring, 0, $startpos).substr($genrestring, $startpos + 1); - $endpos--; - } - $element = substr($genrestring, $startpos + 1, $endpos - ($startpos + 1)); - $genrestring = substr($genrestring, 0, $startpos).substr($genrestring, $endpos + 1); - if (getid3_id3v1::LookupGenreName($element)) { // $element is a valid genre id/abbreviation - - if (empty($returnarray['genre']) || !in_array(getid3_id3v1::LookupGenreName($element), $returnarray['genre'])) { // avoid duplicate entires - $returnarray['genre'][] = getid3_id3v1::LookupGenreName($element); - } - + $clean_genres = array(); + if (strpos($genrestring, "\x00") === false) { + $genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring); + } + $genre_elements = explode("\x00", $genrestring); + foreach ($genre_elements as $element) { + $element = trim($element); + if ($element) { + if (preg_match('#^[0-9]{1,3}#', $element)) { + $clean_genres[] = getid3_id3v1::LookupGenreName($element); } else { - - if (empty($returnarray['genre']) || !in_array($element, $returnarray['genre'])) { // avoid duplicate entires - $returnarray['genre'][] = $element; - } - + $clean_genres[] = str_replace('((', '(', $element); } } } - if ($genrestring) { - if (empty($returnarray['genre']) || !in_array($genrestring, $returnarray['genre'])) { // avoid duplicate entires - $returnarray['genre'][] = $genrestring; - } - } - - return $returnarray; + return $clean_genres; } - function ParseID3v2Frame(&$parsedFrame, &$ThisFileInfo) { + function ParseID3v2Frame(&$parsedFrame) { // shortcuts - $id3v2_majorversion = $ThisFileInfo['id3v2']['majorversion']; + $info = &$this->getid3->info; + $id3v2_majorversion = $info['id3v2']['majorversion']; $parsedFrame['framenamelong'] = $this->FrameNameLongLookup($parsedFrame['frame_name']); if (empty($parsedFrame['framenamelong'])) { @@ -547,21 +560,36 @@ class getid3_id3v2 if ($parsedFrame['flags']['Unsynchronisation']) { $parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']); } + + if ($parsedFrame['flags']['DataLengthIndicator']) { + $parsedFrame['data_length_indicator'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4), 1); + $parsedFrame['data'] = substr($parsedFrame['data'], 4); + } } // Frame-level de-compression if ($parsedFrame['flags']['compression']) { $parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4)); if (!function_exists('gzuncompress')) { - $ThisFileInfo['warning'][] = 'gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"'; - } elseif ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) { - $parsedFrame['data'] = $decompresseddata; + $info['warning'][] = 'gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"'; } else { - $ThisFileInfo['warning'][] = 'gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"'; + if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) { + //if ($decompresseddata = @gzuncompress($parsedFrame['data'])) { + $parsedFrame['data'] = $decompresseddata; + unset($decompresseddata); + } else { + $info['warning'][] = 'gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"'; + } } } } + if (!empty($parsedFrame['flags']['DataLengthIndicator'])) { + if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) { + $info['warning'][] = 'ID3v2 frame "'.$parsedFrame['frame_name'].'" should be '.$parsedFrame['data_length_indicator'].' bytes long according to DataLengthIndicator, but found '.strlen($parsedFrame['data']).' bytes of data'; + } + } + if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) { $warning = 'Frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset'].' has no data portion'; @@ -573,7 +601,7 @@ class getid3_id3v2 default: break; } - $ThisFileInfo['warning'][] = $warning; + $info['warning'][] = $warning; } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1 UFID Unique file identifier (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) { // 4.1 UFI Unique file identifier @@ -582,13 +610,9 @@ class getid3_id3v2 //
// Owner identifier $00 // Identifier - - $frame_terminatorpos = strpos($parsedFrame['data'], "\x00"); - $frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos); - $parsedFrame['ownerid'] = $frame_idstring; - $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00")); - unset($parsedFrame['data']); - + $exploded = explode("\x00", $parsedFrame['data'], 2); + $parsedFrame['ownerid'] = (isset($exploded[0]) ? $exploded[0] : ''); + $parsedFrame['data'] = (isset($exploded[1]) ? $exploded[1] : ''); } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'TXXX')) || // 4.2.2 TXXX User defined text information frame (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'TXX'))) { // 4.2.2 TXX User defined text information frame @@ -603,11 +627,11 @@ class getid3_id3v2 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { - $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; } - $frame_terminatorpos = @strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { - $frame_terminatorpos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00 + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { @@ -619,9 +643,9 @@ class getid3_id3v2 $parsedFrame['description'] = $frame_description; $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { - $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $ThisFileInfo['id3v2']['encoding'], $parsedFrame['data'])); + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data'])); } - unset($parsedFrame['data']); + //unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain } elseif ($parsedFrame['frame_name']{0} == 'T') { // 4.2. T??[?] Text information frame @@ -634,7 +658,7 @@ class getid3_id3v2 $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { - $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; } $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); @@ -643,13 +667,9 @@ class getid3_id3v2 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { - - // remove possible terminating \x00 (put by encoding id or software bug) - $string = getid3_lib::iconv_fallback($parsedFrame['encoding'], $ThisFileInfo['id3v2']['encoding'], $parsedFrame['data']); - if ($string[strlen($string) - 1] == "\x00") { - $string = substr($string, 0, strlen($string) - 1); - } - $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string; + $string = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); + $string = rtrim($string, "\x00"); // remove possible terminating null (put by encoding id or software bug) + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string; unset($string); } @@ -665,11 +685,11 @@ class getid3_id3v2 $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { - $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; } - $frame_terminatorpos = @strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { - $frame_terminatorpos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00 + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); @@ -697,7 +717,7 @@ class getid3_id3v2 $parsedFrame['url'] = $frame_urldata; $parsedFrame['description'] = $frame_description; if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { - $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $ThisFileInfo['id3v2']['encoding'], $parsedFrame['url']); + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['url']); } unset($parsedFrame['data']); @@ -711,7 +731,7 @@ class getid3_id3v2 $parsedFrame['url'] = trim($parsedFrame['data']); if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { - $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['url']; + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['url']; } unset($parsedFrame['data']); @@ -726,14 +746,14 @@ class getid3_id3v2 $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { - $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; } $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($parsedFrame['encodingid']); $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { - $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $ThisFileInfo['id3v2']['encoding'], $parsedFrame['data']); + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); } @@ -744,7 +764,7 @@ class getid3_id3v2 // CD TOC if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { - $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data']; + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data']; } @@ -808,7 +828,7 @@ class getid3_id3v2 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYTC')) || // 4.7 SYTC Synchronised tempo codes - (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) { // 4.8 STC Synchronised tempo codes + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) { // 4.8 STC Synchronised tempo codes // There may only be one 'SYTC' frame in each tag //
// Time stamp format $xx @@ -845,13 +865,13 @@ class getid3_id3v2 $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { - $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; } $frame_language = substr($parsedFrame['data'], $frame_offset, 3); $frame_offset += 3; - $frame_terminatorpos = @strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { - $frame_terminatorpos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00 + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { @@ -867,7 +887,7 @@ class getid3_id3v2 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); $parsedFrame['description'] = $frame_description; if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { - $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $ThisFileInfo['id3v2']['encoding'], $parsedFrame['data']); + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); } unset($parsedFrame['data']); @@ -891,7 +911,7 @@ class getid3_id3v2 $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { - $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; } $frame_language = substr($parsedFrame['data'], $frame_offset, 3); $frame_offset += 3; @@ -942,20 +962,20 @@ class getid3_id3v2 if (strlen($parsedFrame['data']) < 5) { - $ThisFileInfo['warning'][] = 'Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset']; + $info['warning'][] = 'Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset']; } else { $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { - $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; } $frame_language = substr($parsedFrame['data'], $frame_offset, 3); $frame_offset += 3; - $frame_terminatorpos = @strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { - $frame_terminatorpos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00 + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { @@ -971,7 +991,7 @@ class getid3_id3v2 $parsedFrame['description'] = $frame_description; $parsedFrame['data'] = $frame_text; if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { - $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $ThisFileInfo['id3v2']['encoding'], $parsedFrame['data']); + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); } } @@ -996,23 +1016,29 @@ class getid3_id3v2 } $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00")); $parsedFrame['description'] = $frame_idstring; - while (strlen($frame_remainingdata)) { + $RVA2channelcounter = 0; + while (strlen($frame_remainingdata) >= 5) { $frame_offset = 0; $frame_channeltypeid = ord(substr($frame_remainingdata, $frame_offset++, 1)); - $parsedFrame[$frame_channeltypeid]['channeltypeid'] = $frame_channeltypeid; - $parsedFrame[$frame_channeltypeid]['channeltype'] = $this->RVA2ChannelTypeLookup($frame_channeltypeid); - $parsedFrame[$frame_channeltypeid]['volumeadjust'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed + $parsedFrame[$RVA2channelcounter]['channeltypeid'] = $frame_channeltypeid; + $parsedFrame[$RVA2channelcounter]['channeltype'] = $this->RVA2ChannelTypeLookup($frame_channeltypeid); + $parsedFrame[$RVA2channelcounter]['volumeadjust'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed $frame_offset += 2; - $parsedFrame[$frame_channeltypeid]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1)); - $frame_bytespeakvolume = ceil($parsedFrame[$frame_channeltypeid]['bitspeakvolume'] / 8); - $parsedFrame[$frame_channeltypeid]['peakvolume'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume)); + $parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1)); + if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) || ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) { + $info['warning'][] = 'ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value'; + break; + } + $frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8); + $parsedFrame[$RVA2channelcounter]['peakvolume'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume)); $frame_remainingdata = substr($frame_remainingdata, $frame_offset + $frame_bytespeakvolume); + $RVA2channelcounter++; } unset($parsedFrame['data']); } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'RVAD')) || // 4.12 RVAD Relative volume adjustment (ID3v2.3 only) - (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) { // 4.12 RVA Relative volume adjustment (ID3v2.2 only) + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) { // 4.12 RVA Relative volume adjustment (ID3v2.2 only) // There may only be one 'RVA' frame in each tag //
// ID3v2.2 => Increment/decrement %000000ba @@ -1114,7 +1140,7 @@ class getid3_id3v2 $frame_offset = 0; $frame_interpolationmethod = ord(substr($parsedFrame['data'], $frame_offset++, 1)); - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_idstring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_idstring) === 0) { $frame_idstring = ''; @@ -1208,7 +1234,7 @@ class getid3_id3v2 $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { - $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; } if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) { @@ -1216,7 +1242,7 @@ class getid3_id3v2 if (strtolower($frame_imagetype) == 'ima') { // complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted // MIME type instead of 3-char ID3v2.2-format image type (thanks xbhoffØpacbell*net) - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_mimetype) === 0) { $frame_mimetype = ''; @@ -1231,7 +1257,7 @@ class getid3_id3v2 } } if ($id3v2_majorversion > 2 && strlen($parsedFrame['data']) > $frame_offset) { - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_mimetype) === 0) { $frame_mimetype = ''; @@ -1241,40 +1267,88 @@ class getid3_id3v2 $frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1)); - $frame_terminatorpos = @strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); - if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { - $frame_terminatorpos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00 - } - $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); - if (ord($frame_description) === 0) { - $frame_description = ''; - } - $parsedFrame['encodingid'] = $frame_textencoding; - $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); - - if ($id3v2_majorversion == 2) { - $parsedFrame['imagetype'] = $frame_imagetype; + if ($frame_offset >= $parsedFrame['datalength']) { + $info['warning'][] = 'data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset); } else { - $parsedFrame['mime'] = $frame_mimetype; - } - $parsedFrame['picturetypeid'] = $frame_picturetype; - $parsedFrame['picturetype'] = $this->APICPictureTypeLookup($frame_picturetype); - $parsedFrame['description'] = $frame_description; - $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); - - $imageinfo = array(); - $imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo); - if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) { - $parsedFrame['image_mime'] = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]); - if ($imagechunkcheck[0]) { - $parsedFrame['image_width'] = $imagechunkcheck[0]; + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } - if ($imagechunkcheck[1]) { - $parsedFrame['image_height'] = $imagechunkcheck[1]; + $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_description) === 0) { + $frame_description = ''; } - $parsedFrame['image_bytes'] = strlen($parsedFrame['data']); - } + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + if ($id3v2_majorversion == 2) { + $parsedFrame['imagetype'] = $frame_imagetype; + } else { + $parsedFrame['mime'] = $frame_mimetype; + } + $parsedFrame['picturetypeid'] = $frame_picturetype; + $parsedFrame['picturetype'] = $this->APICPictureTypeLookup($frame_picturetype); + $parsedFrame['description'] = $frame_description; + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + $parsedFrame['datalength'] = strlen($parsedFrame['data']); + + $parsedFrame['image_mime'] = ''; + $imageinfo = array(); + $imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo); + if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) { + $parsedFrame['image_mime'] = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]); + if ($imagechunkcheck[0]) { + $parsedFrame['image_width'] = $imagechunkcheck[0]; + } + if ($imagechunkcheck[1]) { + $parsedFrame['image_height'] = $imagechunkcheck[1]; + } + } + + do { + if ($this->inline_attachments === false) { + // skip entirely + unset($parsedFrame['data']); + break; + } + if ($this->inline_attachments === true) { + // great + } elseif (is_int($this->inline_attachments)) { + if ($this->inline_attachments < $parsedFrame['data_length']) { + // too big, skip + $info['warning'][] = 'attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)'; + unset($parsedFrame['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 '.$frame_offset.' cannot be saved to "'.$this->inline_attachments.'" (not writable)'; + unset($parsedFrame['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']).'_'.$frame_offset; + if (!file_exists($destination_filename) || is_writable($destination_filename)) { + file_put_contents($destination_filename, $parsedFrame['data']); + } else { + $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)'; + } + $parsedFrame['data_filename'] = $destination_filename; + unset($parsedFrame['data']); + } else { + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + if (!isset($info['id3v2']['comments']['picture'])) { + $info['id3v2']['comments']['picture'] = array(); + } + $info['id3v2']['comments']['picture'][] = array('data'=>$parsedFrame['data'], 'image_mime'=>$parsedFrame['image_mime']); + } + } + } while (false); + } } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) || // 4.15 GEOB General encapsulated object (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'GEO'))) { // 4.16 GEO General encapsulated object @@ -1290,18 +1364,18 @@ class getid3_id3v2 $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { - $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; } - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_mimetype) === 0) { $frame_mimetype = ''; } $frame_offset = $frame_terminatorpos + strlen("\x00"); - $frame_terminatorpos = @strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { - $frame_terminatorpos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00 + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_filename) === 0) { @@ -1309,9 +1383,9 @@ class getid3_id3v2 } $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); - $frame_terminatorpos = @strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { - $frame_terminatorpos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00 + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { @@ -1341,7 +1415,7 @@ class getid3_id3v2 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POPM')) || // 4.17 POPM Popularimeter - (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) { // 4.18 POP Popularimeter + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) { // 4.18 POP Popularimeter // There may be more than one 'POPM' frame in each tag, // but only one with the same email address //
@@ -1350,16 +1424,16 @@ class getid3_id3v2 // Counter $xx xx xx xx (xx ...) $frame_offset = 0; - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_emailaddress = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_emailaddress) === 0) { $frame_emailaddress = ''; } $frame_offset = $frame_terminatorpos + strlen("\x00"); $frame_rating = ord(substr($parsedFrame['data'], $frame_offset++, 1)); - $parsedFrame['data'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset)); - $parsedFrame['email'] = $frame_emailaddress; - $parsedFrame['rating'] = $frame_rating; + $parsedFrame['counter'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset)); + $parsedFrame['email'] = $frame_emailaddress; + $parsedFrame['rating'] = $frame_rating; unset($parsedFrame['data']); @@ -1390,11 +1464,11 @@ class getid3_id3v2 // Encrypted datablock $frame_offset = 0; - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); $frame_offset = $frame_terminatorpos + strlen("\x00"); - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { $frame_description = ''; @@ -1418,7 +1492,7 @@ class getid3_id3v2 // Encryption info $frame_offset = 0; - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_ownerid) === 0) { $frame_ownerid == ''; @@ -1452,7 +1526,7 @@ class getid3_id3v2 $frame_offset += 4; } - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_url = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_url) === 0) { $frame_url = ''; @@ -1462,7 +1536,7 @@ class getid3_id3v2 $parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset); if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { - $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = utf8_encode($parsedFrame['url']); + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = utf8_encode($parsedFrame['url']); } unset($parsedFrame['data']); @@ -1490,7 +1564,7 @@ class getid3_id3v2 $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { - $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; } $frame_language = substr($parsedFrame['data'], $frame_offset, 3); $frame_offset += 3; @@ -1501,7 +1575,7 @@ class getid3_id3v2 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { - $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $ThisFileInfo['id3v2']['encoding'], $parsedFrame['data']); + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); } unset($parsedFrame['data']); @@ -1517,12 +1591,12 @@ class getid3_id3v2 $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { - $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; } $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_pricepaid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); $frame_offset = $frame_terminatorpos + strlen("\x00"); @@ -1557,10 +1631,10 @@ class getid3_id3v2 $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { - $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; } - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); $frame_offset = $frame_terminatorpos + strlen("\x00"); $frame_rawpricearray = explode('/', $frame_pricestring); @@ -1573,15 +1647,15 @@ class getid3_id3v2 $frame_datestring = substr($parsedFrame['data'], $frame_offset, 8); $frame_offset += 8; - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); $frame_offset = $frame_terminatorpos + strlen("\x00"); $frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1)); - $frame_terminatorpos = @strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { - $frame_terminatorpos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00 + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_sellername) === 0) { @@ -1589,9 +1663,9 @@ class getid3_id3v2 } $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); - $frame_terminatorpos = @strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { - $frame_terminatorpos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00 + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { @@ -1599,7 +1673,7 @@ class getid3_id3v2 } $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); $frame_offset = $frame_terminatorpos + strlen("\x00"); @@ -1629,7 +1703,7 @@ class getid3_id3v2 // Encryption data $frame_offset = 0; - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_ownerid) === 0) { $frame_ownerid = ''; @@ -1652,7 +1726,7 @@ class getid3_id3v2 // Group dependent data $frame_offset = 0; - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_ownerid) === 0) { $frame_ownerid = ''; @@ -1672,7 +1746,7 @@ class getid3_id3v2 // The private data $frame_offset = 0; - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_ownerid) === 0) { $frame_ownerid = ''; @@ -1763,11 +1837,11 @@ class getid3_id3v2 $parsedFrame['album']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']); $parsedFrame['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']); - $ThisFileInfo['replay_gain']['track']['peak'] = $parsedFrame['peakamplitude']; - $ThisFileInfo['replay_gain']['track']['originator'] = $parsedFrame['track']['originator']; - $ThisFileInfo['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment']; - $ThisFileInfo['replay_gain']['album']['originator'] = $parsedFrame['album']['originator']; - $ThisFileInfo['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment']; + $info['replay_gain']['track']['peak'] = $parsedFrame['peakamplitude']; + $info['replay_gain']['track']['originator'] = $parsedFrame['track']['originator']; + $info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment']; + $info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator']; + $info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment']; unset($parsedFrame['data']); @@ -1781,6 +1855,52 @@ class getid3_id3v2 return str_replace("\xFF\x00", "\xFF", $data); } + function LookupExtendedHeaderRestrictionsTagSizeLimits($index) { + static $LookupExtendedHeaderRestrictionsTagSizeLimits = array( + 0x00 => 'No more than 128 frames and 1 MB total tag size', + 0x01 => 'No more than 64 frames and 128 KB total tag size', + 0x02 => 'No more than 32 frames and 40 KB total tag size', + 0x03 => 'No more than 32 frames and 4 KB total tag size', + ); + return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : ''); + } + + function LookupExtendedHeaderRestrictionsTextEncodings($index) { + static $LookupExtendedHeaderRestrictionsTextEncodings = array( + 0x00 => 'No restrictions', + 0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8', + ); + return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : ''); + } + + function LookupExtendedHeaderRestrictionsTextFieldSize($index) { + static $LookupExtendedHeaderRestrictionsTextFieldSize = array( + 0x00 => 'No restrictions', + 0x01 => 'No string is longer than 1024 characters', + 0x02 => 'No string is longer than 128 characters', + 0x03 => 'No string is longer than 30 characters', + ); + return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : ''); + } + + function LookupExtendedHeaderRestrictionsImageEncoding($index) { + static $LookupExtendedHeaderRestrictionsImageEncoding = array( + 0x00 => 'No restrictions', + 0x01 => 'Images are encoded only with PNG or JPEG', + ); + return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : ''); + } + + function LookupExtendedHeaderRestrictionsImageSizeSize($index) { + static $LookupExtendedHeaderRestrictionsImageSizeSize = array( + 0x00 => 'No restrictions', + 0x01 => 'All images are 256x256 pixels or smaller', + 0x02 => 'All images are 64x64 pixels or smaller', + 0x03 => 'All images are exactly 64x64 pixels, unless required otherwise', + ); + return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : ''); + } + function LookupCurrencyUnits($currencyid) { $begin = __LINE__; @@ -2175,7 +2295,7 @@ class getid3_id3v2 - function LanguageLookup($languagecode, $casesensitive=false) { + static function LanguageLookup($languagecode, $casesensitive=false) { if (!$casesensitive) { $languagecode = strtolower($languagecode); @@ -2631,10 +2751,10 @@ class getid3_id3v2 } - function ETCOEventLookup($index) { - if (($index >= 0x17) && ($index <= 0xDF)) { - return 'reserved for future use'; - } + static function ETCOEventLookup($index) { + if (($index >= 0x17) && ($index <= 0xDF)) { + return 'reserved for future use'; + } if (($index >= 0xE0) && ($index <= 0xEF)) { return 'not predefined synch 0-F'; } @@ -2674,7 +2794,7 @@ class getid3_id3v2 return (isset($EventLookup[$index]) ? $EventLookup[$index] : ''); } - function SYTLContentTypeLookup($index) { + static function SYTLContentTypeLookup($index) { static $SYTLContentTypeLookup = array( 0x00 => 'other', 0x01 => 'lyrics', @@ -2690,7 +2810,7 @@ class getid3_id3v2 return (isset($SYTLContentTypeLookup[$index]) ? $SYTLContentTypeLookup[$index] : ''); } - function APICPictureTypeLookup($index, $returnarray=false) { + static function APICPictureTypeLookup($index, $returnarray=false) { static $APICPictureTypeLookup = array( 0x00 => 'Other', 0x01 => '32x32 pixels \'file icon\' (PNG only)', @@ -2720,7 +2840,7 @@ class getid3_id3v2 return (isset($APICPictureTypeLookup[$index]) ? $APICPictureTypeLookup[$index] : ''); } - function COMRReceivedAsLookup($index) { + static function COMRReceivedAsLookup($index) { static $COMRReceivedAsLookup = array( 0x00 => 'Other', 0x01 => 'Standard CD album with other songs', @@ -2736,7 +2856,7 @@ class getid3_id3v2 return (isset($COMRReceivedAsLookup[$index]) ? $COMRReceivedAsLookup[$index] : ''); } - function RVA2ChannelTypeLookup($index) { + static function RVA2ChannelTypeLookup($index) { static $RVA2ChannelTypeLookup = array( 0x00 => 'Other', 0x01 => 'Master volume', @@ -2752,7 +2872,7 @@ class getid3_id3v2 return (isset($RVA2ChannelTypeLookup[$index]) ? $RVA2ChannelTypeLookup[$index] : ''); } - function FrameNameLongLookup($framename) { + static function FrameNameLongLookup($framename) { $begin = __LINE__; @@ -2936,7 +3056,7 @@ class getid3_id3v2 } - function FrameNameShortLookup($framename) { + static function FrameNameShortLookup($framename) { $begin = __LINE__; @@ -2947,8 +3067,8 @@ class getid3_id3v2 ASPI audio_seek_point_index BUF recommended_buffer_size CNT play_counter - COM comments - COMM comments + COM comment + COMM comment COMR commercial_frame CRA audio_encryption CRM encrypted_meta_frame @@ -3115,40 +3235,47 @@ class getid3_id3v2 return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_short'); } - function TextEncodingTerminatorLookup($encoding) { + static function TextEncodingTerminatorLookup($encoding) { // http://www.id3.org/id3v2.4.0-structure.txt // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings: - // $00 ISO-8859-1. Terminated with $00. - // $01 UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00. - // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00. - // $03 UTF-8 encoded Unicode. Terminated with $00. - - static $TextEncodingTerminatorLookup = array(0=>"\x00", 1=>"\x00\x00", 2=>"\x00\x00", 3=>"\x00", 255=>"\x00\x00"); - - return @$TextEncodingTerminatorLookup[$encoding]; + static $TextEncodingTerminatorLookup = array( + 0 => "\x00", // $00 ISO-8859-1. Terminated with $00. + 1 => "\x00\x00", // $01 UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00. + 2 => "\x00\x00", // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00. + 3 => "\x00", // $03 UTF-8 encoded Unicode. Terminated with $00. + 255 => "\x00\x00" + ); + return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : ''); } - function TextEncodingNameLookup($encoding) { + static function TextEncodingNameLookup($encoding) { // http://www.id3.org/id3v2.4.0-structure.txt - static $TextEncodingNameLookup = array(0=>'ISO-8859-1', 1=>'UTF-16', 2=>'UTF-16BE', 3=>'UTF-8', 255=>'UTF-16BE'); + // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings: + static $TextEncodingNameLookup = array( + 0 => 'ISO-8859-1', // $00 ISO-8859-1. Terminated with $00. + 1 => 'UTF-16', // $01 UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00. + 2 => 'UTF-16BE', // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00. + 3 => 'UTF-8', // $03 UTF-8 encoded Unicode. Terminated with $00. + 255 => 'UTF-16BE' + ); return (isset($TextEncodingNameLookup[$encoding]) ? $TextEncodingNameLookup[$encoding] : 'ISO-8859-1'); } - function IsValidID3v2FrameName($framename, $id3v2majorversion) { + static function IsValidID3v2FrameName($framename, $id3v2majorversion) { switch ($id3v2majorversion) { case 2: - return ereg('[A-Z][A-Z0-9]{2}', $framename); + return preg_match('#[A-Z][A-Z0-9]{2}#', $framename); break; case 3: case 4: - return ereg('[A-Z][A-Z0-9]{3}', $framename); + return preg_match('#[A-Z][A-Z0-9]{3}#', $framename); break; } return false; } - function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) { + static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) { for ($i = 0; $i < strlen($numberstring); $i++) { if ((chr($numberstring{$i}) < chr('0')) || (chr($numberstring{$i}) > chr('9'))) { if (($numberstring{$i} == '.') && $allowdecimal) { @@ -3163,11 +3290,11 @@ class getid3_id3v2 return true; } - function IsValidDateStampString($datestamp) { + static function IsValidDateStampString($datestamp) { if (strlen($datestamp) != 8) { return false; } - if (!$this->IsANumber($datestamp, false)) { + if (!self::IsANumber($datestamp, false)) { return false; } $year = substr($datestamp, 0, 4); @@ -3191,7 +3318,7 @@ class getid3_id3v2 return true; } - function ID3v2HeaderLength($majorversion) { + static function ID3v2HeaderLength($majorversion) { return (($majorversion == 2) ? 6 : 10); } diff --git a/apps/media/getID3/getid3/module.tag.lyrics3.php b/3rdparty/getid3/module.tag.lyrics3.php similarity index 61% rename from apps/media/getID3/getid3/module.tag.lyrics3.php rename to 3rdparty/getid3/module.tag.lyrics3.php index 67dba43eb5..aaff717891 100644 --- a/apps/media/getID3/getid3/module.tag.lyrics3.php +++ b/3rdparty/getid3/module.tag.lyrics3.php @@ -14,19 +14,21 @@ ///////////////////////////////////////////////////////////////// -class getid3_lyrics3 +class getid3_lyrics3 extends getid3_handler { - function getid3_lyrics3(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; + // http://www.volweb.cz/str/tags.htm - if ($ThisFileInfo['filesize'] >= pow(2, 31)) { - $ThisFileInfo['warning'][] = 'Unable to check for Lyrics3 because file is larger than 2GB'; + 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($fd, (0 - 128 - 9 - 6), SEEK_END); // end - ID3v1 - LYRICSEND - [Lyrics3size] - $lyrics3_id3v1 = fread($fd, 128 + 9 + 6); + 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 @@ -35,7 +37,7 @@ class getid3_lyrics3 // Lyrics3v1, ID3v1, no APE $lyrics3size = 5100; - $lyrics3offset = $ThisFileInfo['filesize'] - 128 - $lyrics3size; + $lyrics3offset = $info['filesize'] - 128 - $lyrics3size; $lyrics3version = 1; } elseif ($lyrics3end == 'LYRICS200') { @@ -43,49 +45,49 @@ class getid3_lyrics3 // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200' $lyrics3size = $lyrics3lsz + 6 + strlen('LYRICS200'); - $lyrics3offset = $ThisFileInfo['filesize'] - 128 - $lyrics3size; + $lyrics3offset = $info['filesize'] - 128 - $lyrics3size; $lyrics3version = 2; } elseif (substr(strrev($lyrics3_id3v1), 0, 9) == strrev('LYRICSEND')) { // Lyrics3v1, no ID3v1, no APE $lyrics3size = 5100; - $lyrics3offset = $ThisFileInfo['filesize'] - $lyrics3size; + $lyrics3offset = $info['filesize'] - $lyrics3size; $lyrics3version = 1; - $lyrics3offset = $ThisFileInfo['filesize'] - $lyrics3size; + $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 = $ThisFileInfo['filesize'] - $lyrics3size; + $lyrics3offset = $info['filesize'] - $lyrics3size; $lyrics3version = 2; } else { - if (isset($ThisFileInfo['ape']['tag_offset_start']) && ($ThisFileInfo['ape']['tag_offset_start'] > 15)) { + if (isset($info['ape']['tag_offset_start']) && ($info['ape']['tag_offset_start'] > 15)) { - fseek($fd, $ThisFileInfo['ape']['tag_offset_start'] - 15, SEEK_SET); - $lyrics3lsz = fread($fd, 6); - $lyrics3end = fread($fd, 9); + 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 = $ThisFileInfo['ape']['tag_offset_start'] - $lyrics3size; - $ThisFileInfo['avdataend'] = $lyrics3offset; + $lyrics3offset = $info['ape']['tag_offset_start'] - $lyrics3size; + $info['avdataend'] = $lyrics3offset; $lyrics3version = 1; - $ThisFileInfo['warning'][] = 'APE tag located after Lyrics3, will probably break Lyrics3 compatability'; + $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 = $ThisFileInfo['ape']['tag_offset_start'] - $lyrics3size; + $lyrics3offset = $info['ape']['tag_offset_start'] - $lyrics3size; $lyrics3version = 2; - $ThisFileInfo['warning'][] = 'APE tag located after Lyrics3, will probably break Lyrics3 compatability'; + $info['warning'][] = 'APE tag located after Lyrics3, will probably break Lyrics3 compatability'; } @@ -94,14 +96,24 @@ class getid3_lyrics3 } if (isset($lyrics3offset)) { - $ThisFileInfo['avdataend'] = $lyrics3offset; - $this->getLyrics3Data($ThisFileInfo, $fd, $lyrics3offset, $lyrics3version, $lyrics3size); + $info['avdataend'] = $lyrics3offset; + $this->getLyrics3Data($lyrics3offset, $lyrics3version, $lyrics3size); - if (!isset($ThisFileInfo['ape'])) { - $GETID3_ERRORARRAY = &$ThisFileInfo['warning']; + if (!isset($info['ape'])) { + $GETID3_ERRORARRAY = &$info['warning']; if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.apetag.php', __FILE__, false)) { - $tag = new getid3_apetag($fd, $ThisFileInfo, $ThisFileInfo['lyrics3']['tag_offset_start']); - unset($tag); + $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); } } @@ -110,43 +122,46 @@ class getid3_lyrics3 return true; } - function getLyrics3Data(&$ThisFileInfo, &$fd, $endoffset, $version, $length) { + function getLyrics3Data($endoffset, $version, $length) { // http://www.volweb.cz/str/tags.htm - if ($endoffset >= pow(2, 31)) { - $ThisFileInfo['warning'][] = 'Unable to check for Lyrics3 because file is larger than 2GB'; + $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($fd, $endoffset, SEEK_SET); + fseek($this->getid3->fp, $endoffset, SEEK_SET); if ($length <= 0) { return false; } - $rawdata = fread($fd, $length); + $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) { - $ThisFileInfo['warning'][] = '"LYRICSBEGIN" expected at '.$endoffset.' but actually found at '.($endoffset + strpos($rawdata, 'LYRICSBEGIN')).' - this is invalid for Lyrics3 v'.$version; - $ThisFileInfo['avdataend'] = $endoffset + strpos($rawdata, 'LYRICSBEGIN'); - $ParsedLyrics3['tag_offset_start'] = $ThisFileInfo['avdataend']; + $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 { - $ThisFileInfo['error'][] = '"LYRICSBEGIN" expected at '.$endoffset.' but found "'.substr($rawdata, 0, 11).'" instead'; + $info['error'][] = '"LYRICSBEGIN" expected at '.$endoffset.' but found "'.substr($rawdata, 0, 11).'" instead'; return false; } } - $ParsedLyrics3['raw']['lyrics3version'] = $version; - $ParsedLyrics3['raw']['lyrics3tagsize'] = $length; - $ParsedLyrics3['tag_offset_start'] = $endoffset; - $ParsedLyrics3['tag_offset_end'] = $endoffset + $length; - switch ($version) { case 1: @@ -154,7 +169,7 @@ class getid3_lyrics3 $ParsedLyrics3['raw']['LYR'] = trim(substr($rawdata, 11, strlen($rawdata) - 11 - 9)); $this->Lyrics3LyricsTimestampParse($ParsedLyrics3); } else { - $ThisFileInfo['error'][] = '"LYRICSEND" expected at '.(ftell($fd) - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead'; + $info['error'][] = '"LYRICSEND" expected at '.(ftell($this->getid3->fp) - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead'; return false; } break; @@ -192,9 +207,9 @@ class getid3_lyrics3 foreach ($imagestrings as $key => $imagestring) { if (strpos($imagestring, '||') !== false) { $imagearray = explode('||', $imagestring); - $ParsedLyrics3['images'][$key]['filename'] = @$imagearray[0]; - $ParsedLyrics3['images'][$key]['description'] = @$imagearray[1]; - $ParsedLyrics3['images'][$key]['timestamp'] = $this->Lyrics3Timestamp2Seconds(@$imagearray[2]); + $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] : ''); } } } @@ -202,37 +217,37 @@ class getid3_lyrics3 $this->Lyrics3LyricsTimestampParse($ParsedLyrics3); } } else { - $ThisFileInfo['error'][] = '"LYRICS200" expected at '.(ftell($fd) - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead'; + $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: - $ThisFileInfo['error'][] = 'Cannot process Lyrics3 version '.$version.' (only v1 and v2)'; + $info['error'][] = 'Cannot process Lyrics3 version '.$version.' (only v1 and v2)'; return false; break; } - if (isset($ThisFileInfo['id3v1']['tag_offset_start']) && ($ThisFileInfo['id3v1']['tag_offset_start'] < $ParsedLyrics3['tag_offset_end'])) { - $ThisFileInfo['warning'][] = 'ID3v1 tag information ignored since it appears to be a false synch in Lyrics3 tag data'; - unset($ThisFileInfo['id3v1']); - foreach ($ThisFileInfo['warning'] as $key => $value) { + 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($ThisFileInfo['warning'][$key]); - sort($ThisFileInfo['warning']); + unset($info['warning'][$key]); + sort($info['warning']); break; } } } - $ThisFileInfo['lyrics3'] = $ParsedLyrics3; + $info['lyrics3'] = $ParsedLyrics3; return true; } function Lyrics3Timestamp2Seconds($rawtimestamp) { - if (ereg('^\\[([0-9]{2}):([0-9]{2})\\]$', $rawtimestamp, $regs)) { + if (preg_match('#^\\[([0-9]{2}):([0-9]{2})\\]$#', $rawtimestamp, $regs)) { return (int) (($regs[1] * 60) + $regs[2]); } return false; @@ -243,7 +258,7 @@ class getid3_lyrics3 foreach ($lyricsarray as $key => $lyricline) { $regs = array(); unset($thislinetimestamps); - while (ereg('^(\\[[0-9]{2}:[0-9]{2}\\])', $lyricline, $regs)) { + while (preg_match('#^(\\[[0-9]{2}:[0-9]{2}\\])#', $lyricline, $regs)) { $thislinetimestamps[] = $this->Lyrics3Timestamp2Seconds($regs[0]); $lyricline = str_replace($regs[0], '', $lyricline); } diff --git a/3rdparty/getid3/module.tag.xmp.php b/3rdparty/getid3/module.tag.xmp.php new file mode 100644 index 0000000000..141fd09d16 --- /dev/null +++ b/3rdparty/getid3/module.tag.xmp.php @@ -0,0 +1,766 @@ + // +// 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 // +// 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() + */ + var $_sFilename = null; + + /** + * @var array + * The XMP fields that were extracted from the image or updated by this class. + * @see getAllTags() + */ + var $_aXMP = array(); + + /** + * @var boolean + * True if an APP1 segment was found to contain XMP metadata. + * @see isValid() + */ + var $_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. + */ + 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 + */ + 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 + */ + 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 '

This probably is not a JPEG file

'."\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 + */ + 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 + */ + 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'])) + { + // 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'])) + { + 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['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. + */ + 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', +); + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/write.apetag.php b/3rdparty/getid3/write.apetag.php similarity index 92% rename from apps/media/getID3/getid3/write.apetag.php rename to 3rdparty/getid3/write.apetag.php index 189160aff8..2b553699fd 100644 --- a/apps/media/getID3/getid3/write.apetag.php +++ b/3rdparty/getid3/write.apetag.php @@ -21,9 +21,9 @@ class getid3_write_apetag var $filename; var $tag_data; - var $always_preserve_replaygain = true; // ReplayGain / MP3gain tags will be copied from old tag even if not passed in data - var $warnings = array(); // any non-critical errors will be stored here - var $errors = array(); // any critical errors will be stored here + var $always_preserve_replaygain = true; // ReplayGain / MP3gain tags will be copied from old tag even if not passed in data + var $warnings = array(); // any non-critical errors will be stored here + var $errors = array(); // any critical errors will be stored here function getid3_write_apetag() { return true; @@ -56,7 +56,7 @@ class getid3_write_apetag } if ($APEtag = $this->GenerateAPEtag()) { - if ($fp = @fopen($this->filename, 'a+b')) { + if (is_writable($this->filename) && is_file($this->filename) && ($fp = fopen($this->filename, 'a+b'))) { $oldignoreuserabort = ignore_user_abort(true); flock($fp, LOCK_EX); @@ -86,9 +86,7 @@ class getid3_write_apetag fclose($fp); ignore_user_abort($oldignoreuserabort); return true; - } - return false; } return false; } @@ -97,7 +95,7 @@ class getid3_write_apetag $getID3 = new getID3; $ThisFileInfo = $getID3->analyze($this->filename); if (isset($ThisFileInfo['ape']['tag_offset_start']) && isset($ThisFileInfo['ape']['tag_offset_end'])) { - if ($fp = @fopen($this->filename, 'a+b')) { + if (is_writable($this->filename) && is_file($this->filename) && ($fp = fopen($this->filename, 'a+b'))) { flock($fp, LOCK_EX); $oldignoreuserabort = ignore_user_abort(true); @@ -120,7 +118,6 @@ class getid3_write_apetag ignore_user_abort($oldignoreuserabort); return true; - } return false; } @@ -204,7 +201,7 @@ class getid3_write_apetag } function CleanAPEtagItemKey($itemkey) { - $itemkey = eregi_replace("[^\x20-\x7E]", '', $itemkey); + $itemkey = preg_replace("#[^\x20-\x7E]#i", '', $itemkey); // http://www.personal.uni-jena.de/~pfk/mpp/sv8/apekey.html switch (strtoupper($itemkey)) { diff --git a/apps/media/getID3/getid3/write.id3v1.php b/3rdparty/getid3/write.id3v1.php similarity index 53% rename from apps/media/getID3/getid3/write.id3v1.php rename to 3rdparty/getid3/write.id3v1.php index 3c2b7a402c..cecccd8ac5 100644 --- a/apps/media/getID3/getid3/write.id3v1.php +++ b/3rdparty/getid3/write.id3v1.php @@ -18,6 +18,7 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE_ class getid3_write_id3v1 { var $filename; + var $filesize; var $tag_data; var $warnings = array(); // any non-critical errors will be stored here var $errors = array(); // any critical errors will be stored here @@ -27,15 +28,14 @@ class getid3_write_id3v1 } function WriteID3v1() { - if ((filesize($this->filename) >= (pow(2, 31) - 128)) || (filesize($this->filename) < 0)) { - $this->errors[] = 'Unable to write ID3v1 because file is larger than 2GB'; - return false; - } - // File MUST be writeable - CHMOD(646) at least - if (is_writeable($this->filename)) { - if ($fp_source = @fopen($this->filename, 'r+b')) { - + 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 @@ -45,19 +45,19 @@ class getid3_write_id3v1 $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( - @$this->tag_data['title'], - @$this->tag_data['artist'], - @$this->tag_data['album'], - @$this->tag_data['year'], - @$this->tag_data['genreid'], - @$this->tag_data['comment'], - @$this->tag_data['track']); + (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 open '.$this->filename.' mode "r+b"'; + $this->errors[] = 'Could not fopen('.$this->filename.', "r+b")'; return false; } } @@ -71,11 +71,12 @@ class getid3_write_id3v1 // 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 ($ThisFileInfo['filesize'] >= (pow(2, 31) - 128)) { - // cannot write tags on files > 2GB - return false; - } if (isset($ThisFileInfo['tags']['id3v1'])) { foreach ($ThisFileInfo['tags']['id3v1'] as $key => $value) { $id3v1data[$key] = implode(',', $value); @@ -87,18 +88,18 @@ class getid3_write_id3v1 } function RemoveID3v1() { - if ($ThisFileInfo['filesize'] >= pow(2, 31)) { - $this->errors[] = 'Unable to write ID3v1 because file is larger than 2GB'; - return false; - } - // File MUST be writeable - CHMOD(646) at least - if (is_writeable($this->filename)) { - if ($fp_source = @fopen($this->filename, 'r+b')) { + 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, filesize($this->filename) - 128); + ftruncate($fp_source, $this->filesize - 128); } else { // no ID3v1 tag to begin with - do nothing } @@ -106,7 +107,7 @@ class getid3_write_id3v1 return true; } else { - $this->errors[] = 'Could not open '.$this->filename.' mode "r+b"'; + $this->errors[] = 'Could not fopen('.$this->filename.', "r+b")'; } } else { $this->errors[] = $this->filename.' is not writeable'; @@ -114,6 +115,24 @@ class getid3_write_id3v1 return false; } + 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; + } + } ?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/write.id3v2.php b/3rdparty/getid3/write.id3v2.php similarity index 95% rename from apps/media/getID3/getid3/write.id3v2.php rename to 3rdparty/getid3/write.id3v2.php index 32546d18af..ee7c5de2df 100644 --- a/apps/media/getID3/getid3/write.id3v2.php +++ b/3rdparty/getid3/write.id3v2.php @@ -19,6 +19,7 @@ class getid3_write_id3v2 { var $filename; var $tag_data; + var $fread_buffer_size = 32768; // read buffer size in bytes var $paddedlength = 4096; // minimum length of ID3v2 tag in bytes var $majorversion = 3; // ID3v2 major version (2, 3 (recommended), 4) var $minorversion = 0; // ID3v2 minor version - always 0 @@ -36,12 +37,12 @@ class getid3_write_id3v2 // File MUST be writeable - CHMOD(646) at least. It's best if the // directory is also writeable, because that method is both faster and less susceptible to errors. - if (is_writeable($this->filename) || (!file_exists($this->filename) && is_writeable(dirname($this->filename)))) { + if (!empty($this->filename) && (is_writeable($this->filename) || (!file_exists($this->filename) && is_writeable(dirname($this->filename))))) { // Initialize getID3 engine $getID3 = new getID3; $OldThisFileInfo = $getID3->analyze($this->filename); - if ($OldThisFileInfo['filesize'] >= pow(2, 31)) { - $this->errors[] = 'Unable to write ID3v2 because file is larger than 2GB'; + if (!getid3_lib::intValueSupported($OldThisFileInfo['filesize'])) { + $this->errors[] = 'Unable to write ID3v2 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB'; fclose($fp_source); return false; } @@ -51,7 +52,7 @@ class getid3_write_id3v2 $this->tag_data = $this->array_join_merge($OldThisFileInfo['id3v2'], $this->tag_data); } } - $this->paddedlength = max(@$OldThisFileInfo['id3v2']['headerlength'], $this->paddedlength); + $this->paddedlength = (isset($OldThisFileInfo['id3v2']['headerlength']) ? max($OldThisFileInfo['id3v2']['headerlength'], $this->paddedlength) : $this->paddedlength); if ($NewID3v2Tag = $this->GenerateID3v2Tag()) { @@ -60,36 +61,31 @@ class getid3_write_id3v2 // best and fastest method - insert-overwrite existing tag (padded to length of old tag if neccesary) if (file_exists($this->filename)) { - ob_start(); - if ($fp = fopen($this->filename, 'r+b')) { + if (is_readable($this->filename) && is_writable($this->filename) && is_file($this->filename) && ($fp = fopen($this->filename, 'r+b'))) { rewind($fp); fwrite($fp, $NewID3v2Tag, strlen($NewID3v2Tag)); fclose($fp); } else { - $this->errors[] = 'Could not open '.$this->filename.' mode "r+b" - '.strip_tags(ob_get_contents()); + $this->errors[] = 'Could not fopen("'.$this->filename.'", "r+b")'; } - @ob_end_clean(); } else { - ob_start(); - if ($fp = fopen($this->filename, 'wb')) { + if (is_writable($this->filename) && is_file($this->filename) && ($fp = fopen($this->filename, 'wb'))) { rewind($fp); fwrite($fp, $NewID3v2Tag, strlen($NewID3v2Tag)); fclose($fp); } else { - $this->errors[] = 'Could not open '.$this->filename.' mode "wb" - '.strip_tags(ob_get_contents()); + $this->errors[] = 'Could not fopen("'.$this->filename.'", "wb")'; } - @ob_end_clean(); } } else { - if ($tempfilename = tempnam('*', 'getID3')) { - ob_start(); - if ($fp_source = fopen($this->filename, 'rb')) { - if ($fp_temp = fopen($tempfilename, 'wb')) { + if ($tempfilename = tempnam(GETID3_TEMP_DIR, 'getID3')) { + if (is_readable($this->filename) && is_file($this->filename) && ($fp_source = fopen($this->filename, 'rb'))) { + if (is_writable($tempfilename) && is_file($tempfilename) && ($fp_temp = fopen($tempfilename, 'wb'))) { fwrite($fp_temp, $NewID3v2Tag, strlen($NewID3v2Tag)); @@ -98,7 +94,7 @@ class getid3_write_id3v2 fseek($fp_source, $OldThisFileInfo['avdataoffset'], SEEK_SET); } - while ($buffer = fread($fp_source, GETID3_FREAD_BUFFER_SIZE)) { + while ($buffer = fread($fp_source, $this->fread_buffer_size)) { fwrite($fp_temp, $buffer, strlen($buffer)); } @@ -106,22 +102,16 @@ class getid3_write_id3v2 fclose($fp_source); copy($tempfilename, $this->filename); unlink($tempfilename); - @ob_end_clean(); return true; } else { - - $this->errors[] = 'Could not open '.$tempfilename.' mode "wb" - '.strip_tags(ob_get_contents()); - + $this->errors[] = 'Could not fopen("'.$tempfilename.'", "wb")'; } fclose($fp_source); } else { - - $this->errors[] = 'Could not open '.$this->filename.' mode "rb" - '.strip_tags(ob_get_contents()); - + $this->errors[] = 'Could not fopen("'.$this->filename.'", "rb")'; } - @ob_end_clean(); } return false; @@ -138,7 +128,7 @@ class getid3_write_id3v2 } return true; } else { - $this->errors[] = '!is_writeable('.$this->filename.')'; + $this->errors[] = 'WriteID3v2() failed: !is_writeable('.$this->filename.')'; } return false; } @@ -150,12 +140,13 @@ class getid3_write_id3v2 // preferred method - only one copying operation, minimal chance of corrupting // original file if script is interrupted, but required directory to be writeable - if ($fp_source = @fopen($this->filename, 'rb')) { + if (is_readable($this->filename) && is_file($this->filename) && ($fp_source = fopen($this->filename, 'rb'))) { + // Initialize getID3 engine $getID3 = new getID3; $OldThisFileInfo = $getID3->analyze($this->filename); - if ($OldThisFileInfo['filesize'] >= pow(2, 31)) { - $this->errors[] = 'Unable to remove ID3v2 because file is larger than 2GB'; + if (!getid3_lib::intValueSupported($OldThisFileInfo['filesize'])) { + $this->errors[] = 'Unable to remove ID3v2 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB'; fclose($fp_source); return false; } @@ -163,17 +154,17 @@ class getid3_write_id3v2 if ($OldThisFileInfo['avdataoffset'] !== false) { fseek($fp_source, $OldThisFileInfo['avdataoffset'], SEEK_SET); } - if ($fp_temp = @fopen($this->filename.'getid3tmp', 'w+b')) { - while ($buffer = fread($fp_source, GETID3_FREAD_BUFFER_SIZE)) { + if (is_writable($this->filename) && is_file($this->filename) && ($fp_temp = fopen($this->filename.'getid3tmp', 'w+b'))) { + while ($buffer = fread($fp_source, $this->fread_buffer_size)) { fwrite($fp_temp, $buffer, strlen($buffer)); } fclose($fp_temp); } else { - $this->errors[] = 'Could not open '.$this->filename.'getid3tmp mode "w+b"'; + $this->errors[] = 'Could not fopen("'.$this->filename.'getid3tmp", "w+b")'; } fclose($fp_source); } else { - $this->errors[] = 'Could not open '.$this->filename.' mode "rb"'; + $this->errors[] = 'Could not fopen("'.$this->filename.'", "rb")'; } if (file_exists($this->filename)) { unlink($this->filename); @@ -184,12 +175,13 @@ class getid3_write_id3v2 // less desirable alternate method - double-copies the file, overwrites original file // and could corrupt source file if the script is interrupted or an error occurs. - if ($fp_source = @fopen($this->filename, 'rb')) { + if (is_readable($this->filename) && is_file($this->filename) && ($fp_source = fopen($this->filename, 'rb'))) { + // Initialize getID3 engine $getID3 = new getID3; $OldThisFileInfo = $getID3->analyze($this->filename); - if ($OldThisFileInfo['filesize'] >= pow(2, 31)) { - $this->errors[] = 'Unable to remove ID3v2 because file is larger than 2GB'; + if (!getid3_lib::intValueSupported($OldThisFileInfo['filesize'])) { + $this->errors[] = 'Unable to remove ID3v2 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB'; fclose($fp_source); return false; } @@ -198,26 +190,26 @@ class getid3_write_id3v2 fseek($fp_source, $OldThisFileInfo['avdataoffset'], SEEK_SET); } if ($fp_temp = tmpfile()) { - while ($buffer = fread($fp_source, GETID3_FREAD_BUFFER_SIZE)) { + while ($buffer = fread($fp_source, $this->fread_buffer_size)) { fwrite($fp_temp, $buffer, strlen($buffer)); } fclose($fp_source); - if ($fp_source = @fopen($this->filename, 'wb')) { + if (is_writable($this->filename) && is_file($this->filename) && ($fp_source = fopen($this->filename, 'wb'))) { rewind($fp_temp); - while ($buffer = fread($fp_temp, GETID3_FREAD_BUFFER_SIZE)) { + while ($buffer = fread($fp_temp, $this->fread_buffer_size)) { fwrite($fp_source, $buffer, strlen($buffer)); } fseek($fp_temp, -128, SEEK_END); fclose($fp_source); } else { - $this->errors[] = 'Could not open '.$this->filename.' mode "wb"'; + $this->errors[] = 'Could not fopen("'.$this->filename.'", "wb")'; } fclose($fp_temp); } else { $this->errors[] = 'Could not create tmpfile()'; } } else { - $this->errors[] = 'Could not open '.$this->filename.' mode "rb"'; + $this->errors[] = 'Could not fopen("'.$this->filename.'", "rb")'; } } else { @@ -237,25 +229,25 @@ class getid3_write_id3v2 switch ($this->majorversion) { case 4: // %abcd0000 - $flag = (@$flags['unsynchronisation'] ? '1' : '0'); // a - Unsynchronisation - $flag .= (@$flags['extendedheader'] ? '1' : '0'); // b - Extended header - $flag .= (@$flags['experimental'] ? '1' : '0'); // c - Experimental indicator - $flag .= (@$flags['footer'] ? '1' : '0'); // d - Footer present + $flag = (!empty($flags['unsynchronisation']) ? '1' : '0'); // a - Unsynchronisation + $flag .= (!empty($flags['extendedheader'] ) ? '1' : '0'); // b - Extended header + $flag .= (!empty($flags['experimental'] ) ? '1' : '0'); // c - Experimental indicator + $flag .= (!empty($flags['footer'] ) ? '1' : '0'); // d - Footer present $flag .= '0000'; break; case 3: // %abc00000 - $flag = (@$flags['unsynchronisation'] ? '1' : '0'); // a - Unsynchronisation - $flag .= (@$flags['extendedheader'] ? '1' : '0'); // b - Extended header - $flag .= (@$flags['experimental'] ? '1' : '0'); // c - Experimental indicator + $flag = (!empty($flags['unsynchronisation']) ? '1' : '0'); // a - Unsynchronisation + $flag .= (!empty($flags['extendedheader'] ) ? '1' : '0'); // b - Extended header + $flag .= (!empty($flags['experimental'] ) ? '1' : '0'); // c - Experimental indicator $flag .= '00000'; break; case 2: // %ab000000 - $flag = (@$flags['unsynchronisation'] ? '1' : '0'); // a - Unsynchronisation - $flag .= (@$flags['compression'] ? '1' : '0'); // b - Compression + $flag = (!empty($flags['unsynchronisation']) ? '1' : '0'); // a - Unsynchronisation + $flag .= (!empty($flags['compression'] ) ? '1' : '0'); // b - Compression $flag .= '000000'; break; @@ -777,7 +769,7 @@ class getid3_write_id3v2 $framedata .= chr($source_data_array['encodingid']); $framedata .= str_replace("\x00", '', $source_data_array['mime'])."\x00"; $framedata .= chr($source_data_array['picturetypeid']); - $framedata .= @$source_data_array['description'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']); + $framedata .= (!empty($source_data_array['description']) ? $source_data_array['description'] : '').getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']); $framedata .= $source_data_array['data']; } break; @@ -1146,7 +1138,9 @@ class getid3_write_id3v2 break; default: - if ($frame_name{0} == 'T') { + if ((($this->majorversion == 2) && (strlen($frame_name) != 3)) || (($this->majorversion > 2) && (strlen($frame_name) != 4))) { + $this->errors[] = 'Invalid frame name "'.$frame_name.'" for ID3v2.'.$this->majorversion; + } elseif ($frame_name{0} == 'T') { // 4.2. T??? Text information frames // Text encoding $xx // Information @@ -1624,7 +1618,9 @@ class getid3_write_id3v2 if (!$footer && ($this->paddedlength > (strlen($tagstring) + getid3_id3v2::ID3v2HeaderLength($this->majorversion)))) { // pad up to $paddedlength bytes if unpadded tag is shorter than $paddedlength // "Furthermore it MUST NOT have any padding when a tag footer is added to the tag." - $tagstring .= @str_repeat("\x00", $this->paddedlength - strlen($tagstring) - getid3_id3v2::ID3v2HeaderLength($this->majorversion)); + if (($this->paddedlength - strlen($tagstring) - getid3_id3v2::ID3v2HeaderLength($this->majorversion)) > 0) { + $tagstring .= str_repeat("\x00", $this->paddedlength - strlen($tagstring) - getid3_id3v2::ID3v2HeaderLength($this->majorversion)); + } } if ($this->id3v2_use_unsynchronisation && (substr($tagstring, strlen($tagstring) - 1, 1) == "\xFF")) { // special unsynchronisation case: @@ -1822,7 +1818,7 @@ class getid3_write_id3v2 // hashes -> merge based on keys $keys = array_merge(array_keys($arr1), array_keys($arr2)); foreach ($keys as $key) { - $new_array[$key] = $this->array_join_merge(@$arr1[$key], @$arr2[$key]); + $new_array[$key] = $this->array_join_merge((isset($arr1[$key]) ? $arr1[$key] : ''), (isset($arr2[$key]) ? $arr2[$key] : '')); } } else { // two real arrays -> merge @@ -1880,15 +1876,15 @@ class getid3_write_id3v2 if ($parts = $this->safe_parse_url($url)) { if (($parts['scheme'] != 'http') && ($parts['scheme'] != 'https') && ($parts['scheme'] != 'ftp') && ($parts['scheme'] != 'gopher')) { return false; - } elseif (!eregi("^[[:alnum:]]([-.]?[0-9a-z])*\.[a-z]{2,3}$", $parts['host'], $regs) && !IsValidDottedIP($parts['host'])) { + } elseif (!preg_match('#^[[:alnum:]]([-.]?[0-9a-z])*\\.[a-z]{2,3}$#i', $parts['host'], $regs) && !preg_match('#^[0-9]{1,3}(\\.[0-9]{1,3}){3}$#', $parts['host'])) { return false; - } elseif (!eregi("^([[:alnum:]-]|[\_])*$", $parts['user'], $regs)) { + } elseif (!preg_match('#^([[:alnum:]-]|[\\_])*$#i', $parts['user'], $regs)) { return false; - } elseif (!eregi("^([[:alnum:]-]|[\_])*$", $parts['pass'], $regs)) { + } elseif (!preg_match('#^([[:alnum:]-]|[\\_])*$#i', $parts['pass'], $regs)) { return false; - } elseif (!eregi("^[[:alnum:]/_\.@~-]*$", $parts['path'], $regs)) { + } elseif (!preg_match('#^[[:alnum:]/_\\.@~-]*$#i', $parts['path'], $regs)) { return false; - } elseif (!eregi("^[[:alnum:]?&=+:;_()%#/,\.-]*$", $parts['query'], $regs)) { + } elseif (!empty($parts['query']) && !preg_match('#^[[:alnum:]?&=+:;_()%\\#/,\\.-]*$#i', $parts['query'], $regs)) { return false; } else { return true; @@ -1897,7 +1893,7 @@ class getid3_write_id3v2 return false; } - function ID3v2ShortFrameNameLookup($majorversion, $long_description) { + static function ID3v2ShortFrameNameLookup($majorversion, $long_description) { $long_description = str_replace(' ', '_', strtolower(trim($long_description))); static $ID3v2ShortFrameNameLookup = array(); if (empty($ID3v2ShortFrameNameLookup)) { @@ -2045,7 +2041,7 @@ class getid3_write_id3v2 $ID3v2ShortFrameNameLookup[4]['title_sort_order'] = 'TSOT'; $ID3v2ShortFrameNameLookup[4]['set_subtitle'] = 'TSST'; } - return @$ID3v2ShortFrameNameLookup[$majorversion][strtolower($long_description)]; + return (isset($ID3v2ShortFrameNameLookup[$majorversion][strtolower($long_description)]) ? $ID3v2ShortFrameNameLookup[$majorversion][strtolower($long_description)] : ''); } diff --git a/apps/media/getID3/getid3/write.lyrics3.php b/3rdparty/getid3/write.lyrics3.php similarity index 91% rename from apps/media/getID3/getid3/write.lyrics3.php rename to 3rdparty/getid3/write.lyrics3.php index 6b8a47d6a1..fa49cd16ea 100644 --- a/apps/media/getID3/getid3/write.lyrics3.php +++ b/3rdparty/getid3/write.lyrics3.php @@ -30,13 +30,12 @@ class getid3_write_lyrics3 $this->errors[] = 'WriteLyrics3() not yet functional - cannot write Lyrics3'; return false; } - 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 ($fp = @fopen($this->filename, 'a+b')) { + 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); @@ -61,18 +60,14 @@ class getid3_write_lyrics3 return true; } else { - - $this->errors[] = 'Cannot open "'.$this->filename.'" in "a+b" mode'; + $this->errors[] = 'Cannot fopen('.$this->filename.', "a+b")'; return false; - } } // no Lyrics3 present return true; } - - } ?> \ No newline at end of file diff --git a/3rdparty/getid3/write.metaflac.php b/3rdparty/getid3/write.metaflac.php new file mode 100644 index 0000000000..dfd6950aa7 --- /dev/null +++ b/3rdparty/getid3/write.metaflac.php @@ -0,0 +1,163 @@ + // +// 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 +{ + + var $filename; + var $tag_data; + var $warnings = array(); // any non-critical errors will be stored here + var $errors = array(); // any critical errors will be stored here + + function getid3_write_metaflac() { + return true; + } + + 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; + } + + + 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; + } + + + 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 for improving this function + // note: *reg_replace() replaces nulls with empty string (not space) + return strtoupper(preg_replace('#[^ -<>-}]#', ' ', str_replace("\x00", ' ', $originalcommentname))); + + } + +} + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/write.php b/3rdparty/getid3/write.php similarity index 79% rename from apps/media/getID3/getid3/write.php rename to 3rdparty/getid3/write.php index 73e261036f..16b19c7d73 100644 --- a/apps/media/getID3/getid3/write.php +++ b/3rdparty/getid3/write.php @@ -20,10 +20,10 @@ ///////////////////////////////////////////////////////////////// if (!defined('GETID3_INCLUDEPATH')) { - die('getid3.php MUST be included before calling getid3_writetags'); + throw new Exception('getid3.php MUST be included before calling getid3_writetags'); } if (!include_once(GETID3_INCLUDEPATH.'getid3.lib.php')) { - die('write.php depends on getid3.lib.php, which is missing.'); + throw new Exception('write.php depends on getid3.lib.php, which is missing.'); } @@ -51,7 +51,7 @@ class getid3_writetags var $tagformats = array(); // array of tag formats to write ('id3v1', 'id3v2.2', 'id2v2.3', 'id3v2.4', 'ape', 'vorbiscomment', 'metaflac', 'real') var $tag_data = array(array()); // 2-dimensional array of tag data (ex: $data['ARTIST'][0] = 'Elvis') var $tag_encoding = 'ISO-8859-1'; // text encoding used for tag data ('ISO-8859-1', 'UTF-8', 'UTF-16', 'UTF-16LE', 'UTF-16BE', ) - var $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 + var $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 var $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 var $id3v2_tag_language = 'eng'; // ISO-639-2 3-character language code needed for some ID3v2 frames (http://www.id3.org/iso639-2.html) @@ -98,7 +98,7 @@ class getid3_writetags $this->ThisFileInfo = $getID3->analyze($this->filename); // check for what file types are allowed on this fileformat - switch (@$this->ThisFileInfo['fileformat']) { + switch (isset($this->ThisFileInfo['fileformat']) ? $this->ThisFileInfo['fileformat'] : '') { case 'mp3': case 'mp2': case 'mp1': @@ -119,7 +119,7 @@ class getid3_writetags break; case 'ogg': - switch (@$this->ThisFileInfo['audio']['dataformat']) { + 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'; @@ -141,10 +141,8 @@ class getid3_writetags } foreach ($this->tagformats as $requested_tag_format) { if (!in_array($requested_tag_format, $AllowedTagFormats)) { - $errormessage = 'Tag format "'.$requested_tag_format.'" is not allowed on "'.@$this->ThisFileInfo['fileformat']; - if (@$this->ThisFileInfo['fileformat'] != @$this->ThisFileInfo['audio']['dataformat']) { - $errormessage .= '.'.@$this->ThisFileInfo['audio']['dataformat']; - } + $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; @@ -216,7 +214,7 @@ class getid3_writetags // Validation of supplied data if (!is_array($this->tag_data)) { - $this->errors[] = '$tag_data is not an array in WriteTags()'; + $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 @@ -258,7 +256,7 @@ class getid3_writetags 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):
  • '.trim(implode('
  • ', $ape_writer->errors)).'
'; + $this->errors[] = 'WriteAPEtag() failed with message(s):
  • '.str_replace("\n", '
  • ', htmlentities(trim(implode("\n", $ape_writer->errors)))).'
'; } } else { $this->errors[] = 'FormatDataForAPE() failed'; @@ -270,7 +268,7 @@ class getid3_writetags 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):
  • '.trim(implode('
  • ', $id3v1_writer->errors)).'
'; + $this->errors[] = 'WriteID3v1() failed with message(s):
  • '.str_replace("\n", '
  • ', htmlentities(trim(implode("\n", $id3v1_writer->errors)))).'
'; } } else { $this->errors[] = 'FormatDataForID3v1() failed'; @@ -286,7 +284,7 @@ class getid3_writetags 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):
  • '.trim(implode('
  • ', $id3v2_writer->errors)).'
'; + $this->errors[] = 'WriteID3v2() failed with message(s):
  • '.str_replace("\n", '
  • ', htmlentities(trim(implode("\n", $id3v2_writer->errors)))).'
'; } } else { $this->errors[] = 'FormatDataForID3v2() failed'; @@ -298,7 +296,7 @@ class getid3_writetags 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):
  • '.trim(implode('
  • ', $vorbiscomment_writer->errors)).'
'; + $this->errors[] = 'WriteVorbisComment() failed with message(s):
  • '.str_replace("\n", '
  • ', htmlentities(trim(implode("\n", $vorbiscomment_writer->errors)))).'
'; } } else { $this->errors[] = 'FormatDataForVorbisComment() failed'; @@ -310,7 +308,7 @@ class getid3_writetags 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):
  • '.trim(implode('
  • ', $metaflac_writer->errors)).'
'; + $this->errors[] = 'WriteMetaFLAC() failed with message(s):
  • '.str_replace("\n", '
  • ', htmlentities(trim(implode("\n", $metaflac_writer->errors)))).'
'; } } else { $this->errors[] = 'FormatDataForMetaFLAC() failed'; @@ -322,7 +320,7 @@ class getid3_writetags 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):
  • '.trim(implode('
  • ', $real_writer->errors)).'
'; + $this->errors[] = 'WriteReal() failed with message(s):
  • '.str_replace("\n", '
  • ', htmlentities(trim(implode("\n", $real_writer->errors)))).'
'; } } else { $this->errors[] = 'FormatDataForReal() failed'; @@ -421,6 +419,7 @@ class getid3_writetags 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; } @@ -466,14 +465,12 @@ class getid3_writetags } } } - - $tag_data_id3v1['title'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['TITLE'])); - $tag_data_id3v1['artist'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['ARTIST'])); - $tag_data_id3v1['album'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['ALBUM'])); - $tag_data_id3v1['year'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['YEAR'])); - $tag_data_id3v1['comment'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['COMMENT'])); - - $tag_data_id3v1['track'] = intval(getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['TRACKNUMBER']))); + $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'] = ''; } @@ -520,9 +517,35 @@ class getid3_writetags } 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 - $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); + // 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 @@ -578,10 +601,10 @@ class getid3_writetags } function FormatDataForReal() { - $tag_data_real['title'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['TITLE'])); - $tag_data_real['artist'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['ARTIST'])); - $tag_data_real['copyright'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['COPYRIGHT'])); - $tag_data_real['comment'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['COMMENT'])); + $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; diff --git a/3rdparty/getid3/write.real.php b/3rdparty/getid3/write.real.php new file mode 100644 index 0000000000..ad37e74adb --- /dev/null +++ b/3rdparty/getid3/write.real.php @@ -0,0 +1,275 @@ + // +// 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 +{ + var $filename; + var $tag_data = array(); + var $fread_buffer_size = 32768; // read buffer size in bytes + var $warnings = array(); // any non-critical errors will be stored here + var $errors = array(); // any critical errors will be stored here + var $paddedlength = 512; // minimum length of CONT tag in bytes + + function getid3_write_real() { + return true; + } + + 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; + } + + 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; + } + + 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; + } + + 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; + } + + 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; + } + +} + +?> \ No newline at end of file diff --git a/3rdparty/getid3/write.vorbiscomment.php b/3rdparty/getid3/write.vorbiscomment.php new file mode 100644 index 0000000000..ac8dc69343 --- /dev/null +++ b/3rdparty/getid3/write.vorbiscomment.php @@ -0,0 +1,121 @@ + // +// 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 +{ + + var $filename; + var $tag_data; + var $warnings = array(); // any non-critical errors will be stored here + var $errors = array(); // any critical errors will be stored here + + function getid3_write_vorbiscomment() { + return true; + } + + 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; + } + + function DeleteVorbisComment() { + $this->tag_data = array(array()); + return $this->WriteVorbisComment(); + } + + 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 for improving this function + // note: *reg_replace() replaces nulls with empty string (not space) + return strtoupper(preg_replace('#[^ -<>-}]#', ' ', str_replace("\x00", ' ', $originalcommentname))); + + } + +} + +?> \ No newline at end of file diff --git a/3rdparty/granite/git/blob.php b/3rdparty/granite/git/blob.php new file mode 100644 index 0000000000..781a697d56 --- /dev/null +++ b/3rdparty/granite/git/blob.php @@ -0,0 +1,162 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git; +use \Granite\Git\Object\Raw as Raw; +use \InvalidArgumentException as InvalidArgumentException; +use \finfo as finfo; +/** + * **Granite\Git\Blob** represents the raw content of an object in a Git repository, + * typically a **file**. This class provides methods related to the handling of + * blob content, mimetypes, sizes and write support. + * + * @category Git + * @package Granite + * @author Craig Roberts + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ +class Blob +{ + + /** + * Stores the SHA-1 id of the object requested; accessed through the `sha()` + * method where it is recalculated based on the blob content. + */ + private $sha = null; + /** + * The raw binary string of the file contents. + */ + private $content = ""; + /** + * The path to the repository location. + */ + private $path; + + /** + * Fetches a raw Git object and parses the result. Throws an + * InvalidArgumentException if the object is not of the correct type, + * or cannot be found. + * + * @param string $path The path to the repository root. + * @param string $sha The SHA-1 id of the requested object, or `null` if + * creating a new blob object. + * + * @throws InvalidArgumentException If the SHA-1 id provided is not a blob. + */ + public function __construct($path, $sha = NULL) + { + $this->path = $path; + if ($sha !== NULL) { + $this->sha = $sha; + $object = Raw::factory($path, $sha); + + if ($object->type() !== Raw::OBJ_BLOB) { + throw new InvalidArgumentException( + "The object $sha is not a blob, type is {$object->type()}" + ); + } + + $this->content = $object->content(); + unset($object); + } + } + + /** + * Sets or returns the raw file content, depending whether the parameter is + * provided. + * + * @param string $content The object content to set, or `null` if requesting the + * current content. + * + * @return string The raw binary string of the file contents. + */ + public function content($content = NULL) + { + if ($content == NULL) { + return $this->content; + } + $this->content = $content; + } + + /** + * Returns the size of the file content in bytes, equivalent to + * `strlen($blob->content())`. + * + * @return int The size of the object in bytes. + */ + public function size() + { + return strlen($this->content); + } + + /** + * Updates and returns the SHA-1 id of the object, based on it's contents. + * + * @return int The SHA-1 id of the object. + */ + public function sha() + { + $sha = hash_init('sha1'); + $header = 'blob ' . strlen($this->content) . "\0"; + hash_update($sha, $header); + hash_update($sha, $this->content); + $this->sha = hash_final($sha); + return $this->sha; + } + + /** + * Returns the mimetype of the object, using `finfo()` to determine the mimetype + * of the string. + * + * @return string The object mimetype. + * @see http://php.net/manual/en/function.finfo-open.php + */ + public function mimetype() + { + $finfo = new finfo(FILEINFO_MIME); + return $finfo->buffer($this->content); + } + + /** + * Encode and compress the object content, saving it to a 'loose' file. + * + * @return boolean True on success, false on failure. + */ + public function write() + { + $sha = $this->sha(TRUE); + $path = $this->path + . 'objects' + . DIRECTORY_SEPARATOR + . substr($sha, 0, 2) + . DIRECTORY_SEPARATOR + . substr($sha, 2); + // FIXME: currently writes loose objects only + if (file_exists($path)) { + return FALSE; + } + + if (!is_dir(dirname($path))) { + mkdir(dirname($path), 0777, TRUE); + } + + $loose = fopen($path, 'wb'); + $data = 'blob ' . strlen($this->content) . "\0" . $this->content; + $write = fwrite($loose, gzcompress($data)); + fclose($loose); + + return ($write !== FALSE); + } + +} diff --git a/3rdparty/granite/git/commit.php b/3rdparty/granite/git/commit.php new file mode 100644 index 0000000000..51077e89f3 --- /dev/null +++ b/3rdparty/granite/git/commit.php @@ -0,0 +1,232 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git; +use \Granite\Git\Object\Raw as Raw; +use \InvalidArgumentException as InvalidArgumentException; + +/** + * Commit represents a full commit object + * + * @category Git + * @package Granite + * @author Craig Roberts + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ +class Commit +{ + + /** + * The path to the repository root + */ + private $path; + /** + * The SHA-1 id of the requested commit + */ + private $sha; + /** + * The size of the commit in bytes + */ + private $size; + /** + * The commit message + */ + private $message; + /** + * The full committer string + */ + private $committer; + /** + * The full author string + */ + private $author; + /** + * The SHA-1 ids of the parent commits + */ + private $parents = array(); + + /** + * Fetches a raw Git object and parses the result. Throws an + * InvalidArgumentException if the object is not of the correct type, + * or cannot be found. + * + * @param string $path The path to the repository root + * @param string $sha The SHA-1 id of the requested object + * + * @throws InvalidArgumentException + */ + public function __construct($path, $sha = NULL) + { + $this->path = $path; + if ($sha !== NULL) { + $this->sha = $sha; + $object = Raw::factory($path, $sha); + $this->size = $object->size(); + + if ($object->type() !== Raw::OBJ_COMMIT) { + throw new InvalidArgumentException( + "The object $sha is not a commit, type is " . $object->type() + ); + } + + // Parse headers and commit message (delimited with "\n\n") + list($headers, $this->message) = explode("\n\n", $object->content(), 2); + $headers = explode("\n", $headers); + + foreach ($headers as $header) { + list($header, $value) = explode(' ', $header, 2); + if ($header == 'parent') { + $this->parents[] = $value; + } else { + $this->$header = $value; + } + } + + $this->tree = new Tree($this->path, $this->tree); + } + } + + /** + * Returns the message stored in the commit + * + * @return string The commit message + */ + public function message($message = NULL) + { + if ($message !== NULL) { + $this->message = $message; + return $this; + } + return $this->message; + } + + /** + * Returns the commiter string + * + * @return string The committer string + */ + public function committer($committer = NULL) + { + if ($committer !== NULL) { + $this->committer = $committer; + return $this; + } + return $this->committer; + } + + /** + * Returns the author string + * + * @return string The author string + */ + public function author($author = NULL) + { + if ($author !== NULL) { + $this->author = $author; + return $this; + } + return $this->author; + } + + /** + * Returns the parents of the commit, or an empty array if none + * + * @return array The parents of the commit + */ + public function parents($parents = NULL) + { + if ($parents !== NULL) { + $this->parents = $parents; + return $this; + } + return $this->parents; + } + + /** + * Returns a tree object associated with the commit + * + * @return Tree + */ + public function tree(Tree $tree = NULL) + { + if ($tree !== NULL) { + $this->tree = $tree; + return $this; + } + return $this->tree; + } + + /** + * Returns the size of the commit in bytes (Git header + data) + * + * @return int + */ + public function size() + { + return $this->size; + } + + /** + * Returns the size of the commit in bytes (Git header + data) + * + * @return int + */ + public function sha() + { + $this->sha = hash('sha1', $this->_raw()); + return $this->sha; + } + + public function write() + { + $sha = $this->sha(); + $path = $this->path + . 'objects' + . DIRECTORY_SEPARATOR + . substr($sha, 0, 2) + . DIRECTORY_SEPARATOR + . substr($sha, 2); + // FIXME: currently writes loose objects only + if (file_exists($path)) { + return FALSE; + } + + if (!is_dir(dirname($path))) { + mkdir(dirname($path), 0777, TRUE); + } + + $loose = fopen($path, 'wb'); + $data = $this->_raw(); + $write = fwrite($loose, gzcompress($data)); + fclose($loose); + + return ($write !== FALSE); + } + + public function _raw() + { + $data = 'tree ' . $this->tree->sha() . "\n"; + foreach ($this->parents as $parent) + { + $data .= "parent $parent\n"; + } + $data .= 'author ' . $this->author . "\n"; + $data .= 'committer ' . $this->committer . "\n\n"; + $data .= $this->message; + + $data = 'commit ' . strlen($data) . "\0" . $data; + return $data; + } + +} diff --git a/3rdparty/granite/git/object/index.php b/3rdparty/granite/git/object/index.php new file mode 100644 index 0000000000..239706d4ef --- /dev/null +++ b/3rdparty/granite/git/object/index.php @@ -0,0 +1,210 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git\Object; +use \UnexpectedValueException as UnexpectedValueException; + +/** + * Index represents a packfile index + * + * @category Git + * @package Granite + * @author Craig Roberts + * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @link http://craig0990.github.com/Granite/ + */ +class Index +{ + const INDEX_MAGIC = "\377tOc"; + + /** + * The full path to the packfile index + */ + private $path; + /** + * The offset at which the fanout begins, version 2+ indexes have a 2-byte header + */ + private $offset = 8; + /** + * The size of the SHA-1 entries, version 1 stores 4-byte offsets alongside to + * total 24 bytes, version 2+ stores offsets separately + */ + private $size = 20; + /** + * The version of the index file format, versions 1 and 2 are in use and + * currently supported + */ + private $version; + + /** + * Fetches a raw Git object and parses the result + * + * @param string $path The path to the repository root + * @param string $packname The name of the packfile index to read + */ + public function __construct($path, $packname) + { + $this->path = $path + . 'objects' + . DIRECTORY_SEPARATOR + . 'pack' + . DIRECTORY_SEPARATOR + . 'pack-' . $packname . '.idx'; + + $this->version = $this->_readVersion(); + if ($this->version !== 1 && $this->version !== 2) { + throw new UnexpectedValueException( + "Unsupported index version (version $version)" + ); + } + + if ($this->version == 1) { + $this->offset = 0; // Version 1 index has no header/version + $this->size = 24; // Offsets + SHA-1 ids are stored together + } + } + + /** + * Returns the offset of the object stored in the index + * + * @param string $sha The SHA-1 id of the object being requested + * + * @return int The offset of the object in the packfile + */ + public function find($sha) + { + $index = fopen($this->path, 'rb'); + $offset = false; // Offset for object in packfile not found by default + + // Read the fanout to skip to the start char in the sorted SHA-1 list + list($start, $after) = $this->_readFanout($index, $sha); + + if ($start == $after) { + fclose($index); + return false; // Object is apparently located in a 0-length section + } + + // Seek $offset + 255 4-byte fanout entries and read 256th entry + fseek($index, $this->offset + 4 * 255); + $totalObjects = $this->_uint32($index); + + // Look up the SHA-1 id of the object + // TODO: Binary search + fseek($index, $this->offset + 1024 + $this->size * $start); + for ($i = $start; $i < $after; $i++) { + if ($this->version == 1) { + $offset = $this->_uint32($index); + } + + $name = fread($index, 20); + if ($name == pack('H40', $sha)) { + break; // Found it + } + } + + if ($i == $after) { + fclose($index); + return false; // Scanned entire section, couldn't find it + } + + if ($this->version == 2) { + // Jump to the offset location and read it + fseek($index, 1032 + 24 * $totalObjects + 4 * $i); + $offset = $this->_uint32($index); + if ($offset & 0x80000000) { + // Offset is a 64-bit integer; packfile is larger than 2GB + fclose($index); + throw new UnexpectedValueException( + "Packfile larger than 2GB, currently unsupported" + ); + } + } + + fclose($index); + return $offset; + } + + /** + * Converts a binary string into a 32-bit unsigned integer + * + * @param handle $file Binary string to convert + * + * @return int Integer value + */ + private function _uint32($file) + { + $val = unpack('Nx', fread($file, 4)); + return $val['x']; + } + + /** + * Reads the fanout for a particular SHA-1 id + * + * Largely modified from Glip, with some reference to Grit - largely because I + * can't see how to re-implement this in PHP + * + * @param handle $file File handle to the index file + * @param string $sha The SHA-1 id to search for + * @param int $offset The offset at which the fanout begins + * + * @return array Array containing integer 'start' and + * 'past-the-end' locations + */ + private function _readFanout($file, $sha) + { + $sha = pack('H40', $sha); + fseek($file, $this->offset); + if ($sha{0} == "\00") { + /** + * First character is 0, read first fanout entry to provide + * 'past-the-end' location (since first fanout entry provides start + * point for '1'-prefixed SHA-1 ids) + */ + $start = 0; + fseek($file, $this->offset); // Jump to start of fanout, $offset bytes in + $after = $this->_uint32($file); + } else { + /** + * Take ASCII value of first character, minus one to get the fanout + * position of the offset (minus one because the fanout does not + * contain an entry for "\00"), multiplied by four bytes per entry + */ + fseek($file, $this->offset + (ord($sha{0}) - 1) * 4); + $start = $this->_uint32($file); + $after = $this->_uint32($file); + } + + return array($start, $after); + } + + /** + * Returns the version number of the index file, or 1 if there is no version + * information + * + * @return int + */ + private function _readVersion() + { + $file = fopen($this->path, 'rb'); + $magic = fread($file, 4); + $version = $this->_uint32($file); + + if ($magic !== self::INDEX_MAGIC) { + $version = 1; + } + + fclose($file); + return $version; + } + +} diff --git a/3rdparty/granite/git/object/loose.php b/3rdparty/granite/git/object/loose.php new file mode 100644 index 0000000000..32f894845b --- /dev/null +++ b/3rdparty/granite/git/object/loose.php @@ -0,0 +1,81 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git\Object; +use \UnexpectedValueException as UnexpectedValueException; + +/** + * Loose represents a loose object in the Git repository + * + * @category Git + * @package Granite + * @author Craig Roberts + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ +class Loose extends Raw +{ + + /** + * Reads an object from a loose object file based on the SHA-1 id + * + * @param string $path The path to the repository root + * @param string $sha The SHA-1 id of the requested object + * + * @throws UnexpectedValueException If the type is not 'commit', 'tree', + * 'tag' or 'blob' + */ + public function __construct($path, $sha) + { + $this->sha = $sha; + + $loose_path = $path + . 'objects/' + . substr($sha, 0, 2) + . '/' + . substr($sha, 2); + + if (!file_exists($loose_path)) { + throw new InvalidArgumentException("Cannot open loose object file for $sha"); + } + + $raw = gzuncompress(file_get_contents($loose_path)); + $data = explode("\0", $raw, 2); + + $header = $data[0]; + $this->content = $data[1]; + + list($this->type, $this->size) = explode(' ', $header); + + switch ($this->type) { + case 'commit': + $this->type = Raw::OBJ_COMMIT; + break; + case 'tree': + $this->type = Raw::OBJ_TREE; + break; + case 'blob': + $this->type = Raw::OBJ_BLOB; + break; + case 'tag': + $this->type = Raw::OBJ_TAG; + break; + default: + throw new UnexpectedValueException( + "Unexpected type '{$this->type}'" + ); + break; + } + } + +} diff --git a/3rdparty/granite/git/object/packed.php b/3rdparty/granite/git/object/packed.php new file mode 100644 index 0000000000..7e8d663b32 --- /dev/null +++ b/3rdparty/granite/git/object/packed.php @@ -0,0 +1,304 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git\Object; +use \UnexpectedValueException as UnexpectedValueException; + +/** + * Packed represents a packed object in the Git repository + * + * @category Git + * @package Granite + * @author Craig Roberts + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ +class Packed extends Raw +{ + + /** + * The name of the packfile being read + */ + private $_packfile; + + /** + * Added to the object size to make a 'best-guess' effort at how much compressed + * data to read - should be reimplemented, ideally with streams. + */ + const OBJ_PADDING = 512; + + /** + * Reads the object data from the compressed data at $offset in $packfile + * + * @param string $packfile The path to the packfile + * @param int $offset The offset of the object data + */ + public function __construct($packfile, $offset) + { + $this->_packfile = $packfile; + + list($this->type, $this->size, $this->content) + = $this->_readPackedObject($offset); + } + + /** + * Reads the object data at $this->_offset + * + * @param int $offset Offset of the object header + * + * @return array Containing the type, size and object data + */ + private function _readPackedObject($offset) + { + $file = fopen($this->_packfile, 'rb'); + fseek($file, $offset); + // Read the type and uncompressed size from the object header + list($type, $size) = $this->_readHeader($file, $offset); + $object_offset = ftell($file); + + if ($type == self::OBJ_OFS_DELTA || $type == self::OBJ_REF_DELTA) { + return $this->_unpackDeltified( + $file, $offset, $object_offset, $type, $size + ); + } + + $content = gzuncompress(fread($file, $size + self::OBJ_PADDING), $size); + + return array($type, $size, $content); + } + + /** + * Reads a packed object header, returning the type and the size. For more + * detailed information, refer to the @see tag. + * + * From the @see tag: "Each byte is really 7 bits of data, with the first bit + * being used to say if that hunk is the last one or not before the data starts. + * If the first bit is a 1, you will read another byte, otherwise the data starts + * next. The first 3 bits in the first byte specifies the type of data..." + * + * @param handle $file File handle to read + * @param int $offset Offset of the object header + * + * @return array Containing the type and the size + * @see http://book.git-scm.com/7_the_packfile.html + */ + private function _readHeader($file, $offset) + { + // Read the object header byte-by-byte + fseek($file, $offset); + $byte = ord(fgetc($file)); + /** + * Bit-shift right by four, then ignore the first bit with a bitwise AND + * This gives us the object type in binary: + * 001 commit self::OBJ_COMMIT + * 010 tree self::OBJ_TREE + * 011 blob self::OBJ_BLOB + * 100 tag self::OBJ_TAG + * 110 offset delta self::OBJ_OFS_DELTA + * 111 ref delta self::OBJ_REF_DELTA + * + * (000 is undefined, 101 is not currently in use) + * See http://book.git-scm.com/7_the_packfile.html for details + */ + $type = ($byte >> 4) & 0x07; + + // Read the last four bits of the first byte, used to find the size + $size = $byte & 0x0F; + + /** + * $shift initially set to four, since we use the last four bits of the first + * byte + * + * $byte & 0x80 checks the initial bit is set to 1 (i.e. keep reading data) + * + * Finally, $shift is incremented by seven for each consecutive byte (because + * we ignore the initial bit) + */ + for ($shift = 4; $byte & 0x80; $shift += 7) { + $byte = ord(fgetc($file)); + /** + * The size is ANDed against 0x7F to strip the initial bit, then + * bitshifted by left $shift (4 or 7, depending on whether it's the + * initial byte) and ORed against the existing binary $size. This + * continuously increments the $size variable. + */ + $size |= (($byte & 0x7F) << $shift); + } + + return array($type, $size); + } + + /** + * Unpacks a deltified object located at $offset in $file + * + * @param handle $file File handle to read + * @param int $offset Offset of the object data + * @param int $object_offset Offset of the object data, past the header + * @param int $type The object type, either OBJ_REF_DELTA + or OBJ_OFS_DELTA + * @param int $size The expected size of the uncompressed data + * + * @return array Containing the type, size and object data + */ + private function _unpackDeltified($file, $offset, $object_offset, $type, $size) + { + fseek($file, $object_offset); + + if ($type == self::OBJ_REF_DELTA) { + + $base_sha = bin2hex(fread($file, 20)); + + $path = substr($this->_packfile, 0, strpos($this->_packfile, '.git')+5); + $base = Raw::factory($path, $base_sha); + $type = $base->type(); + $base = $base->content(); + + $delta = gzuncompress( + fread($file, $size + self::OBJ_PADDING), $size + ); + + $content = $this->_applyDelta($base, $delta); + + } elseif ($type == self::OBJ_OFS_DELTA) { + + // 20 = maximum varint size according to Glip + $data = fread($file, $size + self::OBJ_PADDING + 20); + + list($base_offset, $length) = $this->_bigEndianNumber($data); + + $delta = gzuncompress(substr($data, $length), $size); + unset($data); + + $base_offset = $offset - $base_offset; + list($type, $size, $base) = $this->_readPackedObject($base_offset); + + $content = $this->_applyDelta($base, $delta); + + } else { + throw new UnexpectedValueException( + "Unknown type $type for deltified object" + ); + } + + return array($type, strlen($content), $content); + } + + /** + * Applies the $delta byte-sequence to $base and returns the + * resultant binary string. + * + * This code is modified from Grit (see below), the Ruby + * implementation used for GitHub under an MIT license. + * + * @param string $base The base string for the delta to be applied to + * @param string $delta The delta string to apply + * + * @return string The patched binary string + * @see + * https://github.com/mojombo/grit/blob/master/lib/grit/git-ruby/internal/pack.rb + */ + private function _applyDelta($base, $delta) + { + $pos = 0; + $src_size = $this->_varint($delta, $pos); + $dst_size = $this->_varint($delta, $pos); + + if ($src_size !== strlen($base)) { + throw new UnexpectedValueException( + 'Expected base delta size ' . strlen($base) . ' does not match the expected ' + . "value $src_size" + ); + } + + $dest = ""; + while ($pos < strlen($delta)) { + $byte = ord($delta{$pos++}); + + if ($byte & 0x80) { + /* copy a part of $base */ + $offset = 0; + if ($byte & 0x01) $offset = ord($delta{$pos++}); + if ($byte & 0x02) $offset |= ord($delta{$pos++}) << 8; + if ($byte & 0x04) $offset |= ord($delta{$pos++}) << 16; + if ($byte & 0x08) $offset |= ord($delta{$pos++}) << 24; + $length = 0; + if ($byte & 0x10) $length = ord($delta{$pos++}); + if ($byte & 0x20) $length |= ord($delta{$pos++}) << 8; + if ($byte & 0x40) $length |= ord($delta{$pos++}) << 16; + if ($length == 0) $length = 0x10000; + $dest .= substr($base, $offset, $length); + } else { + /* take the next $byte bytes as they are */ + $dest .= substr($delta, $pos, $byte); + $pos += $byte; + } + } + + if (strlen($dest) !== $dst_size) { + throw new UnexpectedValueException( + "Deltified string expected to be $dst_size bytes, but actually " + . strlen($dest) . ' bytes' + ); + } + + return $dest; + } + + /** + * Parse a Git varint (variable-length integer). Used in the `_applyDelta()` + * method to read the delta header. + * + * @param string $string The string to parse + * @param int &$pos The position in the string to read from + * + * @return int The integer value + */ + private function _varint($string, &$pos = 0) + { + $varint = 0; + $bitmask = 0x80; + for ($i = 0; $bitmask & 0x80; $i += 7) { + $bitmask = ord($string{$pos++}); + $varint |= (($bitmask & 0x7F) << $i); + } + return $varint; + } + + /** + * Decodes a big endian modified base 128 number (refer to @see tag); this only + * appears to be used in one place, the offset delta in packfiles. The offset + * is the number of bytes to seek back from the start of the delta object to find + * the base object. + * + * This code has been implemented using the C code given in the @see tag below. + * + * @param string &$data The data to read from and decode the number + * + * @return Array Containing the base offset (number of bytes to seek back) and + * the length to use when reading the delta + * @see http://git.rsbx.net/Documents/Git_Data_Formats.txt + */ + private function _bigEndianNumber(&$data) + { + $i = 0; + $byte = ord($data{$i++}); + $number = $byte & 0x7F; + while ($byte & 0x80) { + $byte = ord($data{$i++}); + $number = (($number + 1) << 7) | ($byte & 0x7F); + } + + return array($number, $i); + } + +} diff --git a/3rdparty/granite/git/object/raw.php b/3rdparty/granite/git/object/raw.php new file mode 100644 index 0000000000..56f363c37b --- /dev/null +++ b/3rdparty/granite/git/object/raw.php @@ -0,0 +1,153 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git\Object; +use \InvalidArgumentException as InvalidArgumentException; + +/** + * Raw represents a raw Git object, using Index to locate + * packed objects. + * + * @category Git + * @package Granite + * @author Craig Roberts + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ +class Raw +{ + /** + * Integer values for Git objects + * @see http://book.git-scm.com/7_the_packfile.html + */ + const OBJ_COMMIT = 1; + const OBJ_TREE = 2; + const OBJ_BLOB = 3; + const OBJ_TAG = 4; + const OBJ_OFS_DELTA = 6; + const OBJ_REF_DELTA = 7; + + /** + * The SHA-1 id of the requested object + */ + protected $sha; + /** + * The type of the requested object (see class constants) + */ + protected $type; + /** + * The binary string content of the requested object + */ + protected $content; + + /** + * Returns an instance of a raw Git object + * + * @param string $path The path to the repository root + * @param string $sha The SHA-1 id of the requested object + * + * @return Packed|Loose + */ + public static function factory($path, $sha) + { + $loose_path = $path + . 'objects/' + . substr($sha, 0, 2) + . '/' + . substr($sha, 2); + if (file_exists($loose_path)) { + return new Loose($path, $sha); + } else { + return self::_findPackedObject($path, $sha); + } + } + + /** + * Returns the raw content of the Git object requested + * + * @return string Raw object content + */ + public function content() + { + return $this->content; + } + + /** + * Returns the size of the Git object + * + * @return int The size of the object in bytes + */ + public function size() + { + return strlen($this->content); + } + + /** + * Returns the type of the object as either commit, tag, blob or tree + * + * @return string The object type + */ + public function type() + { + return $this->type; + } + + /** + * Searches a packfile for the SHA id and reads the object from the packfile + * + * @param string $path The path to the repository + * @param string $sha The SHA-1 id of the object being requested + * + * @throws \InvalidArgumentException + * @return array An array containing the type, size and object data + */ + private static function _findPackedObject($path, $sha) + { + $packfiles = glob( + $path + . 'objects' + . DIRECTORY_SEPARATOR + . 'pack' + . DIRECTORY_SEPARATOR + . 'pack-*.pack' + ); + + $offset = false; + foreach ($packfiles as $packfile) { + $packname = substr(basename($packfile, '.pack'), 5); + $idx = new Index($path, $packname); + $offset = $idx->find($sha); + + if ($offset !== false) { + break; // Found it + } + } + + if ($offset == false) { + throw new InvalidArgumentException("Could not find packed object $sha"); + } + + $packname = $path + . 'objects' + . DIRECTORY_SEPARATOR + . 'pack' + . DIRECTORY_SEPARATOR + . 'pack-' . $packname . '.pack'; + $object = new Packed($packname, $offset); + + return $object; + } + +} + +?> diff --git a/3rdparty/granite/git/repository.php b/3rdparty/granite/git/repository.php new file mode 100644 index 0000000000..30b58a39f5 --- /dev/null +++ b/3rdparty/granite/git/repository.php @@ -0,0 +1,293 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git; +use \InvalidArgumentException as InvalidArgumentException; +use \UnexpectedValueException as UnexpectedValueException; + +/** + * Repository represents a Git repository, providing a variety of methods for + * fetching objects from SHA-1 ids or the tip of a branch with `head()` + * + * @category Git + * @package Granite + * @author Craig Roberts + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ +class Repository +{ + + /** + * The path to the repository root + */ + private $_path; + /** + * The indexed version of a commit, ready to write with `commit()` + */ + private $idx_commit; + /** + * The indexed version of a tree, modified to with `add()` and `remove()` + */ + private $idx_tree; + + /** + * Sets the repository path + * + * @param string $path The path to the repository root (i.e. /repo/.git/) + */ + public function __construct($path) + { + if (!is_dir($path)) { + throw new InvalidArgumentException("Unable to find directory $path"); + } elseif (!is_readable($path)) { + throw new InvalidArgumentException("Unable to read directory $path"); + } elseif (!is_dir($path . DIRECTORY_SEPARATOR . 'objects') + || !is_dir($path . DIRECTORY_SEPARATOR . 'refs') + ) { + throw new UnexpectedValueException( + "Invalid directory, could not find 'objects' or 'refs' in $path" + ); + } + + $this->_path = $path; + $this->idx_commit = $this->factory('commit'); + $this->idx_tree = $this->factory('tree'); + } + + /** + * Returns an object from the Repository of the given type, with the given + * SHA-1 id, or false if it cannot be found + * + * @param string $type The type (blob, commit, tag or tree) of object being + * requested + * @param string $sha The SHA-1 id of the object (or the name of a tag) + * + * @return Blob|Commit|Tag|Tree + */ + public function factory($type, $sha = null) + { + if (!in_array($type, array('blob', 'commit', 'tag', 'tree'))) { + throw new InvalidArgumentException("Invalid type: $type"); + } + + if ($type == 'tag') { + $sha = $this->_ref('tags' . DIRECTORY_SEPARATOR . $sha); + } + $type = 'Granite\\Git\\' . ucwords($type); + + return new $type($this->_path, $sha); + } + + /** + * Returns a Commit object representing the HEAD commit + * + * @param string $branch The branch name to lookup, defaults to 'master' + * + * @return Commit An object representing the HEAD commit + */ + public function head($branch = 'master', $value = NULL) + { + if ($value == NULL) + return $this->factory( + 'commit', $this->_ref('heads' . DIRECTORY_SEPARATOR . $branch) + ); + + file_put_contents( + $this->_path . DIRECTORY_SEPARATOR + . 'refs' . DIRECTORY_SEPARATOR + . 'heads' . DIRECTORY_SEPARATOR . 'master', + $value + ); + } + + /** + * Returns a string representing the repository's location, which may or may + * not be initialised + * + * @return string A string representing the repository's location + */ + public function path() + { + return $this->_path; + } + + /** + * Returns an array of the local branches under `refs/heads` + * + * @return array + */ + public function tags() + { + return $this->_refs('tags'); + } + + /** + * Returns an array of the local tags under `refs/tags` + * + * @return array + */ + public function branches() + { + return $this->_refs('heads'); + } + + private function _refs($type) + { + $dir = $this->_path . 'refs' . DIRECTORY_SEPARATOR . $type; + $refs = glob($dir . DIRECTORY_SEPARATOR . '*'); + foreach ($refs as &$ref) { + $ref = basename($ref); + } + return $refs; + } + + /** + * Initialises a Git repository + * + * @return boolean Returns true on success, false on error + */ + public static function init($path) + { + $path .= '/'; + if (!is_dir($path)) { + mkdir($path); + } elseif (is_dir($path . 'objects')) { + return false; + } + + mkdir($path . 'objects'); + mkdir($path . 'objects/info'); + mkdir($path . 'objects/pack'); + mkdir($path . 'refs'); + mkdir($path . 'refs/heads'); + mkdir($path . 'refs/tags'); + + file_put_contents($path . 'HEAD', 'ref: refs/heads/master'); + + return true; + } + + /** + * Writes the indexed commit to disk, with blobs added/removed via `add()` and + * `rm()` + * + * @param string $message The commit message + * @param string $author The author name + * + * @return boolean True on success, or false on failure + */ + public function commit($message, $author) + { + $user_string = $username . ' ' . time() . ' +0000'; + + try { + $parents = array($this->repo->head()->sha()); + } catch (InvalidArgumentException $e) { + $parents = array(); + } + + $this->idx_commit->message($message); + $this->idx_commit->author($user_string); + $this->idx_commit->committer($user_string); + $this->idx_commit->tree($this->idx_tree); + $commit->parents($parents); + + $this->idx_tree->write(); + $this->idx_commit->write(); + + $this->repo->head('master', $this->idx_commit->sha()); + + $this->idx_commit = $this->factory('commit'); + $this->idx_tree = $this->factory('tree'); + } + + /** + * Adds a file to the indexed commit, to be written to disk with `commit()` + * + * @param string $filename The filename to save it under + * @param Granite\Git\Blob $blob The raw blob object to add to the tree + */ + public function add($filename, Granite\Git\Blob $blob) + { + $blob->write(); + $nodes = $this->idx_tree->nodes(); + $nodes[$filename] = new Granite\Git\Tree\Node($filename, '100644', $blob->sha()); + $this->idx_tree->nodes($nodes); + } + + /** + * Removes a file from the indexed commit + */ + public function rm($filename) + { + $nodes = $this->idx_tree->nodes(); + unset($nodes[$filename]); + $this->idx_tree->nodes($nodes); + } + + /** + * Returns an SHA-1 id of the ref resource + * + * @param string $ref The ref name to lookup + * + * @return string An SHA-1 id of the ref resource + */ + private function _ref($ref) + { + // All refs are stored in `.git/refs` + $file = $this->_path . 'refs' . DIRECTORY_SEPARATOR . $ref; + + if (file_exists($file)) { + return trim(file_get_contents($file)); + } + + $sha = $this->_packedRef($ref); + + if ($sha == false) { + throw new InvalidArgumentException("The ref $ref could not be found"); + } + + return $sha; + } + + /** + * Returns an SHA-1 id of the ref resource, or false if it cannot be found + * + * @param string $ref The ref name to lookup + * + * @return string An SHA-1 id of the ref resource + */ + private function _packedRef($ref) + { + $sha = false; + if (file_exists($this->_path . 'packed-refs')) { + $file = fopen($this->_path . 'packed-refs', 'r'); + + while (($line = fgets($file)) !== false) { + $info = explode(' ', $line); + if (count($info) == 2 + && trim($info[1]) == 'refs' . DIRECTORY_SEPARATOR . $ref + ) { + $sha = trim($info[0]); + break; + } + } + + fclose($file); + } + + return $sha; + } + +} diff --git a/3rdparty/granite/git/tag.php b/3rdparty/granite/git/tag.php new file mode 100644 index 0000000000..e26ddaffa6 --- /dev/null +++ b/3rdparty/granite/git/tag.php @@ -0,0 +1,38 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git; + +/** + * Tag represents a full tag object + * + * @category Git + * @package Granite + * @author Craig Roberts + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ +class Tag +{ + + public function __construct($path, $sha) + { + $this->sha = $sha; + } + + public function sha() + { + return $this->sha; + } + +} diff --git a/3rdparty/granite/git/tree.php b/3rdparty/granite/git/tree.php new file mode 100644 index 0000000000..2de7227453 --- /dev/null +++ b/3rdparty/granite/git/tree.php @@ -0,0 +1,198 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git; +use \Granite\Git\Tree\Node as Node; + +/** + * Tree represents a full tree object, with nodes pointing to other tree objects + * and file blobs + * + * @category Git + * @package Granite + * @author Craig Roberts + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ +class Tree +{ + + /** + * The SHA-1 id of the requested tree + */ + private $sha; + /** + * The nodes/entries for the requested tree + */ + private $nodes = array(); + /** + * The path to the repository + */ + private $path; + + /** + * Reads a tree object by fetching the raw object + * + * @param string $path The path to the repository root + * @param string $sha The SHA-1 id of the requested object + */ + public function __construct($path, $sha = NULL, $dbg = FALSE) + { + $this->path = $path; + if ($sha !== NULL) { + $object = Object\Raw::factory($path, $sha); + $this->sha = $sha; + + if ($object->type() !== Object\Raw::OBJ_TREE) { + throw new \InvalidArgumentException( + "The object $sha is not a tree, type is " . $object->type() + ); + } + + $content = $object->content(); + file_put_contents('/tmp/tree_from_real_repo'.time(), $content); + $nodes = array(); + + for ($i = 0; $i < strlen($content); $i = $data_start + 21) { + $data_start = strpos($content, "\0", $i); + $info = substr($content, $i, $data_start-$i); + list($mode, $name) = explode(' ', $info, 2); + // Read the object SHA-1 id + $sha = bin2hex(substr($content, $data_start + 1, 20)); + + $this->nodes[$name] = new Node($name, $mode, $sha); + } + } + } + + /** + * Returns an array of Tree and Granite\Git\Blob objects, + * representing subdirectories and files + * + * @return array Array of Tree and Granite\Git\Blob objects + */ + public function nodes($nodes = null) + { + if ($nodes == null) { + return $this->nodes; + } + $this->nodes = $nodes; + } + + /** + * Adds a blob or a tree to the list of nodes + * + * @param string $name The basename (filename) of the blob or tree + * @param string $mode The mode of the blob or tree (see above) + * @param string $sha The SHA-1 id of the blob or tree to add + */ + public function add($name, $mode, $sha) + { + $this->nodes[$name] = new Node($name, $mode, $sha); + uasort($this->nodes, array($this, '_sort')); + } + + public function write() + { + $sha = $this->sha(); + $path = $this->path + . 'objects' + . DIRECTORY_SEPARATOR + . substr($sha, 0, 2) + . DIRECTORY_SEPARATOR + . substr($sha, 2); + // FIXME: currently writes loose objects only + if (file_exists($path)) { + return FALSE; + } + + if (!is_dir(dirname($path))) { + mkdir(dirname($path), 0777, TRUE); + } + + $loose = fopen($path, 'wb'); + $data = $this->_raw(); + $data = 'tree ' . strlen($data) . "\0" . $data; + $write = fwrite($loose, gzcompress($data)); + fclose($loose); + + return ($write !== FALSE); + } + + /** + * Returns the SHA-1 id of the Tree + * + * @return string SHA-1 id of the Tree + */ + public function sha() + { + $data = $this->_raw(); + $raw = 'tree ' . strlen($data) . "\0" . $data; + $this->sha = hash('sha1', $raw); + return $this->sha; + } + + /** + * Generates the raw object content to be saved to disk + */ + public function _raw() + { + uasort($this->nodes, array($this, '_sort')); + $data = ''; + foreach ($this->nodes as $node) + { + $data .= base_convert($node->mode(), 10, 8) . ' ' . $node->name() . "\0"; + $data .= pack('H40', $node->sha()); + } + file_put_contents('/tmp/tree_made'.time(), $data); + return $data; + } + + /** + * Sorts the node entries in a tree, general sort method adapted from original + * Git C code (see @see tag below). + * + * @return 1, 0 or -1 if the first entry is greater than, the same as, or less + * than the second, respectively. + * @see https://github.com/gitster/git/blob/master/read-cache.c Around line 352, + * the `base_name_compare` function + */ + public function _sort(&$a, &$b) + { + $length = strlen($a->name()) < strlen($b->name()) ? strlen($a->name()) : strlen($b->name()); + + $cmp = strncmp($a->name(), $b->name(), $length); + if ($cmp) { + return $cmp; + } + + $suffix1 = $a->name(); + $suffix1 = (strlen($suffix1) > $length) ? $suffix1{$length} : FALSE; + $suffix2 = $b->name(); + $suffix2 = (strlen($suffix2) > $length) ? $suffix2{$length} : FALSE; + if (!$suffix1 && $a->isDirectory()) { + $suffix1 = '/'; + } + if (!$suffix2 && $b->isDirectory()) { + $suffix2 = '/'; + } + if ($suffix1 < $suffix2) { + return -1; + } elseif ($suffix1 > $suffix2) { + return 1; + } + + return 0; + } + +} diff --git a/3rdparty/granite/git/tree/node.php b/3rdparty/granite/git/tree/node.php new file mode 100644 index 0000000000..f99eb1ae28 --- /dev/null +++ b/3rdparty/granite/git/tree/node.php @@ -0,0 +1,126 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git\Tree; + +/** + * Node represents an entry in a Tree + * + * @category Git + * @package Granite + * @author Craig Roberts + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ +class Node +{ + + /** + * Name of the file, directory or submodule + */ + private $_name; + /** + * Mode of the object, in octal + */ + private $_mode; + /** + * SHA-1 id of the tree + */ + private $_sha; + /** + * Boolean value for whether the entry represents a directory + */ + private $_is_dir; + /** + * Boolean value for whether the entry represents a submodule + */ + private $_is_submodule; + + /** + * Sets up a Node class with properties corresponding to the $mode parameter + * + * @param string $name The name of the object (file, directory or submodule name) + * @param int $mode The mode of the object, retrieved from the repository + * @param string $sha The SHA-1 id of the object + */ + public function __construct($name, $mode, $sha) + { + $this->_name = $name; + $this->_mode = intval($mode, 8); + $this->_sha = $sha; + + $this->_is_dir = (bool) ($this->_mode & 0x4000); + $this->_is_submodule = ($this->_mode == 0xE000); + } + + /** + * Returns a boolean value indicating whether the node is a directory + * + * @return boolean + */ + public function isDirectory() + { + return $this->_is_dir; + } + + /** + * Returns a boolean value indicating whether the node is a submodule + * + * @return boolean + */ + public function isSubmodule() + { + return $this->_is_submodule; + } + + /** + * Returns the object name + * + * @return string + */ + public function name() + { + return $this->_name; + } + + /** + * Returns the object's SHA-1 id + * + * @return string + */ + public function sha() + { + return $this->_sha; + } + + /** + * Returns the octal value of the file mode + * + * @return int + */ + public function mode() + { + return $this->_mode; + } + + public function type() + { + if ($this->isDirectory()) { + return 'tree'; + } elseif ($this->isSubmodule()) { + return 'commit'; + } else { + return 'blob'; + } + } +} diff --git a/AUTHORS b/AUTHORS index 02439884d2..f618df9311 100644 --- a/AUTHORS +++ b/AUTHORS @@ -5,6 +5,13 @@ ownCloud is written by: Jan-Christoph Borchardt Michael Gapczynski Arthur Schiwon + Bart Visscher + Georg Ehrke + Brice Maron + Tom Needham + Marvin Thomas Rabe + Florian Pritz + Bartek Przybylski … With help from many libraries and frameworks including: diff --git a/README b/README index 4d4be2728e..77379a4645 100644 --- a/README +++ b/README @@ -3,10 +3,11 @@ A personal cloud which runs on your own server. http://ownCloud.org -Installation instructions: http://owncloud.org/support/setup-and-installation/ -Source code: http://gitorious.org/owncloud +Installation instructions: http://owncloud.org/support +Source code: http://gitorious.org/owncloud Mailing list: http://mail.kde.org/mailman/listinfo/owncloud IRC channel: http://webchat.freenode.net/?channels=owncloud Diaspora: https://joindiaspora.com/u/owncloud Identi.ca: http://identi.ca/owncloud + diff --git a/apps/admin_dependencies_chk/appinfo/app.php b/apps/admin_dependencies_chk/appinfo/app.php index e2169b5dd7..f282b103c8 100644 --- a/apps/admin_dependencies_chk/appinfo/app.php +++ b/apps/admin_dependencies_chk/appinfo/app.php @@ -1,5 +1,5 @@ 14, diff --git a/apps/admin_dependencies_chk/appinfo/info.xml b/apps/admin_dependencies_chk/appinfo/info.xml index 10721ece15..d5f8bd3783 100644 --- a/apps/admin_dependencies_chk/appinfo/info.xml +++ b/apps/admin_dependencies_chk/appinfo/info.xml @@ -2,10 +2,8 @@ admin_dependencies_chk Owncloud dependencies info - 0.01 AGPL Brice Maron (eMerzh) 2 Display OwnCloud's dependencies informations (missings modules, ...) - diff --git a/apps/admin_dependencies_chk/appinfo/version b/apps/admin_dependencies_chk/appinfo/version new file mode 100644 index 0000000000..d1c6331b31 --- /dev/null +++ b/apps/admin_dependencies_chk/appinfo/version @@ -0,0 +1 @@ +0.01 \ No newline at end of file diff --git a/apps/admin_dependencies_chk/settings.php b/apps/admin_dependencies_chk/settings.php index ce90dd604c..ea1ce9fb3d 100644 --- a/apps/admin_dependencies_chk/settings.php +++ b/apps/admin_dependencies_chk/settings.php @@ -20,7 +20,7 @@ * License along with this library. If not, see . * */ -$l=new OC_L10N('admin_dependencies_chk'); +$l=OC_L10N::get('admin_dependencies_chk'); $tmpl = new OC_Template( 'admin_dependencies_chk', 'settings'); $modules = array(); diff --git a/apps/admin_export/appinfo/info.xml b/apps/admin_export/appinfo/info.xml deleted file mode 100644 index df8a07c2f5..0000000000 --- a/apps/admin_export/appinfo/info.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - admin_export - Import/Export - Import/Export your owncloud data - 0.1 - AGPL - Thomas Schmidt - 2 - - diff --git a/apps/admin_export/settings.php b/apps/admin_export/settings.php deleted file mode 100644 index a33c872ccf..0000000000 --- a/apps/admin_export/settings.php +++ /dev/null @@ -1,96 +0,0 @@ -. - * - */ -OC_Util::checkAdminUser(); -OC_Util::checkAppEnabled('admin_export'); -if (isset($_POST['admin_export'])) { - $root = OC::$SERVERROOT . "/"; - $zip = new ZipArchive(); - $filename = get_temp_dir() . "/owncloud_export_" . date("y-m-d_H-i-s") . ".zip"; - OC_Log::write('admin_export',"Creating export file at: " . $filename,OC_Log::INFO); - if ($zip->open($filename, ZIPARCHIVE::CREATE) !== TRUE) { - exit("Cannot open <$filename>\n"); - } - - if (isset($_POST['owncloud_system'])) { - // adding owncloud system files - OC_Log::write('admin_export',"Adding owncloud system files to export",OC_Log::INFO); - zipAddDir($root, $zip, false); - foreach (array(".git", "3rdparty", "apps", "core", "files", "l10n", "lib", "ocs", "search", "settings", "tests") as $dirname) { - zipAddDir($root . $dirname, $zip, true, basename($root) . "/"); - } - } - - if (isset($_POST['owncloud_config'])) { - // adding owncloud config - // todo: add database export - OC_Log::write('admin_export',"Adding owncloud config to export",OC_Log::INFO); - zipAddDir($root . "config/", $zip, true, basename($root) . "/"); - $zip->addFile($root . '/data/.htaccess', basename($root) . "/data/owncloud.db"); - } - - if (isset($_POST['user_files'])) { - // adding user files - $zip->addFile($root . '/data/.htaccess', basename($root) . "/data/.htaccess"); - $zip->addFile($root . '/data/index.html', basename($root) . "/data/index.html"); - foreach (OC_User::getUsers() as $i) { - OC_Log::write('admin_export',"Adding owncloud user files of $i to export",OC_Log::INFO); - zipAddDir($root . "data/" . $i, $zip, true, basename($root) . "/data/"); - } - } - - $zip->close(); - - header("Content-Type: application/zip"); - header("Content-Disposition: attachment; filename=" . basename($filename)); - header("Content-Length: " . filesize($filename)); - @ob_end_clean(); - readfile($filename); - unlink($filename); -} else { -// fill template - $tmpl = new OC_Template('admin_export', 'settings'); - return $tmpl->fetchPage(); -} - -function zipAddDir($dir, $zip, $recursive=true, $internalDir='') { - $dirname = basename($dir); - $zip->addEmptyDir($internalDir . $dirname); - $internalDir.=$dirname.='/'; - - if ($dirhandle = opendir($dir)) { - while (false !== ( $file = readdir($dirhandle))) { - - if (( $file != '.' ) && ( $file != '..' )) { - - if (is_dir($dir . '/' . $file) && $recursive) { - zipAddDir($dir . '/' . $file, $zip, $recursive, $internalDir); - } elseif (is_file($dir . '/' . $file)) { - $zip->addFile($dir . '/' . $file, $internalDir . $file); - } - } - } - closedir($dirhandle); - } else { - OC_Log::write('admin_export',"Was not able to open directory: " . $dir,OC_Log::ERROR); - } -} diff --git a/apps/admin_export/templates/settings.php b/apps/admin_export/templates/settings.php deleted file mode 100644 index 47689facbb..0000000000 --- a/apps/admin_export/templates/settings.php +++ /dev/null @@ -1,13 +0,0 @@ -
-
- t('Export this ownCloud instance');?> -

t('This will create a compressed file that contains the data of this owncloud instance. - Please choose which components should be included:');?> -

-


-
- -

- -
-
diff --git a/apps/admin_export/appinfo/app.php b/apps/admin_migrate/appinfo/app.php similarity index 74% rename from apps/admin_export/appinfo/app.php rename to apps/admin_migrate/appinfo/app.php index beebb4864e..e45d3f6a52 100644 --- a/apps/admin_export/appinfo/app.php +++ b/apps/admin_migrate/appinfo/app.php @@ -1,10 +1,10 @@ "admin_export_settings", + 'id' => "admin_migrate_settings", 'order'=>1, - 'href' => OC_Helper::linkTo( "admin_export", "settings.php" ), + 'href' => OC_Helper::linkTo( "admin_migrate", "settings.php" ), 'name' => 'Export' ); diff --git a/apps/admin_migrate/appinfo/info.xml b/apps/admin_migrate/appinfo/info.xml new file mode 100644 index 0000000000..5172157900 --- /dev/null +++ b/apps/admin_migrate/appinfo/info.xml @@ -0,0 +1,10 @@ + + + admin_migrate + ownCloud Instance Migration + Import/Export your owncloud instance + AGPL + Thomas Schmidt and Tom Needham + 2 + + diff --git a/apps/admin_migrate/appinfo/version b/apps/admin_migrate/appinfo/version new file mode 100644 index 0000000000..ceab6e11ec --- /dev/null +++ b/apps/admin_migrate/appinfo/version @@ -0,0 +1 @@ +0.1 \ No newline at end of file diff --git a/apps/admin_migrate/settings.php b/apps/admin_migrate/settings.php new file mode 100644 index 0000000000..94bf6052a6 --- /dev/null +++ b/apps/admin_migrate/settings.php @@ -0,0 +1,57 @@ +. + * + */ +OC_Util::checkAdminUser(); +OC_Util::checkAppEnabled('admin_migrate'); + +// Export? +if (isset($_POST['admin_export'])) { + // Create the export zip + $response = json_decode( OC_Migrate::export( null, $_POST['export_type'] ) ); + if( !$response->success ){ + // Error + die('error'); + } else { + $path = $response->data; + // Download it + header("Content-Type: application/zip"); + header("Content-Disposition: attachment; filename=" . basename($path)); + header("Content-Length: " . filesize($path)); + @ob_end_clean(); + readfile( $path ); + unlink( $path ); + } +// Import? +} else if( isset($_POST['admin_import']) ){ + $from = $_FILES['owncloud_import']['tmp_name']; + + if( !OC_Migrate::import( $from, 'instance' ) ){ + die('failed'); + } + +} else { +// fill template + $tmpl = new OC_Template('admin_migrate', 'settings'); + return $tmpl->fetchPage(); +} \ No newline at end of file diff --git a/apps/admin_migrate/templates/settings.php b/apps/admin_migrate/templates/settings.php new file mode 100644 index 0000000000..91e305074e --- /dev/null +++ b/apps/admin_migrate/templates/settings.php @@ -0,0 +1,31 @@ +
+
+ t('Export this ownCloud instance');?> +

t('This will create a compressed file that contains the data of this owncloud instance. + Please choose the export type:');?> +

+

What would you like to export?

+

+ ownCloud instance (suitable for import )
+ ownCloud system files
+ Just user files
+ +

+
+ +
+
+ t('Import an ownCloud instance. THIS WILL DELETE ALL CURRENT OWNCLOUD DATA');?> +

t('All current ownCloud data will be replaced by the ownCloud instance that is uploaded.');?> +

+

+

+ +
+
+ diff --git a/apps/bookmarks/appinfo/app.php b/apps/bookmarks/appinfo/app.php index 09d7b5df52..b9c308ca05 100644 --- a/apps/bookmarks/appinfo/app.php +++ b/apps/bookmarks/appinfo/app.php @@ -17,4 +17,5 @@ OC_App::addNavigationEntry( array( 'id' => 'bookmarks_index', 'order' => 70, 'hr OC_App::registerPersonal('bookmarks', 'settings'); OC_Util::addScript('bookmarks','bookmarksearch'); + OC_Search::registerProvider('OC_Search_Provider_Bookmarks'); diff --git a/apps/bookmarks/appinfo/info.xml b/apps/bookmarks/appinfo/info.xml index 862ab805a6..39779483d8 100644 --- a/apps/bookmarks/appinfo/info.xml +++ b/apps/bookmarks/appinfo/info.xml @@ -3,7 +3,6 @@ bookmarks Bookmarks Bookmark manager for ownCloud - 0.2 AGPL Arthur Schiwon, Marvin Thomas Rabe 2 diff --git a/apps/bookmarks/appinfo/migrate.php b/apps/bookmarks/appinfo/migrate.php new file mode 100644 index 0000000000..c1251e816e --- /dev/null +++ b/apps/bookmarks/appinfo/migrate.php @@ -0,0 +1,68 @@ +'bookmarks', + 'matchcol'=>'user_id', + 'matchval'=>$this->uid, + 'idcol'=>'id' + ); + $ids = $this->content->copyRows( $options ); + + $options = array( + 'table'=>'bookmarks_tags', + 'matchcol'=>'bookmark_id', + 'matchval'=>$ids + ); + + // Export tags + $ids2 = $this->content->copyRows( $options ); + + // If both returned some ids then they worked + if( is_array( $ids ) && is_array( $ids2 ) ) + { + return true; + } else { + return false; + } + + } + + // Import function for bookmarks + function import( ){ + switch( $this->appinfo->version ){ + default: + // All versions of the app have had the same db structure, so all can use the same import function + $query = $this->content->prepare( "SELECT * FROM bookmarks WHERE user_id LIKE ?" ); + $results = $query->execute( array( $this->olduid ) ); + $idmap = array(); + while( $row = $results->fetchRow() ){ + // Import each bookmark, saving its id into the map + $query = OC_DB::prepare( "INSERT INTO *PREFIX*bookmarks(url, title, user_id, public, added, lastmodified) VALUES (?, ?, ?, ?, ?, ?)" ); + $query->execute( array( $row['url'], $row['title'], $this->uid, $row['public'], $row['added'], $row['lastmodified'] ) ); + // Map the id + $idmap[$row['id']] = OC_DB::insertid(); + } + // Now tags + foreach($idmap as $oldid => $newid){ + $query = $this->content->prepare( "SELECT * FROM bookmarks_tags WHERE bookmark_id LIKE ?" ); + $results = $query->execute( array( $oldid ) ); + while( $row = $results->fetchRow() ){ + // Import the tags for this bookmark, using the new bookmark id + $query = OC_DB::prepare( "INSERT INTO *PREFIX*bookmarks_tags(bookmark_id, tag) VALUES (?, ?)" ); + $query->execute( array( $newid, $row['tag'] ) ); + } + } + // All done! + break; + } + + return true; + } + +} + +// Load the provider +new OC_Migration_Provider_Bookmarks( 'bookmarks' ); \ No newline at end of file diff --git a/apps/bookmarks/appinfo/version b/apps/bookmarks/appinfo/version new file mode 100644 index 0000000000..2f4536184b --- /dev/null +++ b/apps/bookmarks/appinfo/version @@ -0,0 +1 @@ +0.2 \ No newline at end of file diff --git a/apps/bookmarks/bookmarksHelper.php b/apps/bookmarks/bookmarksHelper.php index 8def7401e2..f1464be79d 100644 --- a/apps/bookmarks/bookmarksHelper.php +++ b/apps/bookmarks/bookmarksHelper.php @@ -71,7 +71,7 @@ function getURLMetadata($url) { return $metadata; } -function addBookmark($url, $title='', $tags='') { +function addBookmark($url, $title, $tags='') { $CONFIG_DBTYPE = OC_Config::getValue( "dbtype", "sqlite" ); if( $CONFIG_DBTYPE == 'sqlite' or $CONFIG_DBTYPE == 'sqlite3' ){ $_ut = "strftime('%s','now')"; @@ -93,6 +93,11 @@ function addBookmark($url, $title='', $tags='') { $title = $metadata['title']; } + if(empty($title)) { + $l = OC_L10N::get('bookmarks'); + $title = $l->t('unnamed'); + } + $params=array( htmlspecialchars_decode($url), htmlspecialchars_decode($title), diff --git a/apps/bookmarks/js/bookmarks.js b/apps/bookmarks/js/bookmarks.js index fa5adde254..9502af0a00 100644 --- a/apps/bookmarks/js/bookmarks.js +++ b/apps/bookmarks/js/bookmarks.js @@ -9,9 +9,7 @@ $(document).ready(function() { fillWindow($('.bookmarks_list')); }); $(window).resize(); - $($('.bookmarks_list')).scroll(updateOnBottom); - - $('.bookmarks_list').empty(); + $('.bookmarks_list').scroll(updateOnBottom).empty().width($('#content').width()); getBookmarks(); }); @@ -145,7 +143,7 @@ function updateBookmarksList(bookmark) { '

'+ '' + encodeEntities(bookmark.title) + '' + '

' + - '

' + encodeEntities(bookmark.url) + '

' + + '

' + encodeEntities(bookmark.url) + '

' + '' ); if(taglist != '') { diff --git a/apps/bookmarks/lib/search.php b/apps/bookmarks/lib/search.php index 235587855d..d7e3255861 100644 --- a/apps/bookmarks/lib/search.php +++ b/apps/bookmarks/lib/search.php @@ -20,8 +20,8 @@ * */ -class OC_Search_Provider_Bookmarks implements OC_Search_Provider{ - static function search($query){ +class OC_Search_Provider_Bookmarks extends OC_Search_Provider{ + function search($query){ $results=array(); $offset = 0; diff --git a/apps/bookmarks/templates/bookmarklet.php b/apps/bookmarks/templates/bookmarklet.php index 5ea67f04df..f7074462a7 100644 --- a/apps/bookmarks/templates/bookmarklet.php +++ b/apps/bookmarks/templates/bookmarklet.php @@ -1,7 +1,7 @@ ' . $l->t('Drag this to your browser bookmarks and click it, when you want to bookmark a webpage quickly:') . '' . '' . $l->t('Read later') . ''; diff --git a/apps/calendar/ajax/calendar/new.form.php b/apps/calendar/ajax/calendar/new.form.php index 6e7423cbe9..fa014351f7 100644 --- a/apps/calendar/ajax/calendar/new.form.php +++ b/apps/calendar/ajax/calendar/new.form.php @@ -7,7 +7,6 @@ */ require_once('../../../../lib/base.php'); -$l10n = new OC_L10N('calendar'); OC_JSON::checkLoggedIn(); OC_JSON::checkAppEnabled('calendar'); $calendarcolor_options = OC_Calendar_Calendar::getCalendarColorOptions(); diff --git a/apps/calendar/ajax/calendar/overview.php b/apps/calendar/ajax/calendar/overview.php index 2f73f5d071..dd55f3e018 100644 --- a/apps/calendar/ajax/calendar/overview.php +++ b/apps/calendar/ajax/calendar/overview.php @@ -7,7 +7,7 @@ */ require_once('../../../../lib/base.php'); -$l10n = new OC_L10N('calendar'); +$l10n = OC_L10N::get('calendar'); OC_JSON::checkLoggedIn(); OC_JSON::checkAppEnabled('calendar'); $output = new OC_TEMPLATE("calendar", "part.choosecalendar"); diff --git a/apps/calendar/ajax/categories/rescan.php b/apps/calendar/ajax/categories/rescan.php new file mode 100644 index 0000000000..0fd878ed8f --- /dev/null +++ b/apps/calendar/ajax/categories/rescan.php @@ -0,0 +1,42 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +require_once('../../../../lib/base.php'); +OC_JSON::checkLoggedIn(); +OC_JSON::checkAppEnabled('calendar'); + +foreach ($_POST as $key=>$element) { + debug('_POST: '.$key.'=>'.print_r($element, true)); +} + +function bailOut($msg) { + OC_JSON::error(array('data' => array('message' => $msg))); + OC_Log::write('calendar','ajax/categories/rescan.php: '.$msg, OC_Log::DEBUG); + exit(); +} +function debug($msg) { + OC_Log::write('calendar','ajax/categories/rescan.php: '.$msg, OC_Log::DEBUG); +} + +$calendars = OC_Calendar_Calendar::allCalendars(OC_User::getUser()); +if(count($calendars) == 0) { + bailOut(OC_Calendar_App::$l10n->t('No calendars found.')); +} +$events = array(); +foreach($calendars as $calendar) { + $calendar_events = OC_Calendar_Object::all($calendar['id']); + $events = $events + $calendar_events; +} +if(count($events) == 0) { + bailOut(OC_Calendar_App::$l10n->t('No events found.')); +} + +OC_Calendar_App::scanCategories($events); +$categories = OC_Calendar_App::getCategoryOptions(); + +OC_JSON::success(array('data' => array('categories'=>$categories))); diff --git a/apps/calendar/ajax/event/delete.php b/apps/calendar/ajax/event/delete.php index 862dec6bf5..5fc12900ef 100644 --- a/apps/calendar/ajax/event/delete.php +++ b/apps/calendar/ajax/event/delete.php @@ -7,8 +7,6 @@ */ require_once('../../../../lib/base.php'); -$l10n = new OC_L10N('calendar'); - OC_JSON::checkLoggedIn(); OC_JSON::checkAppEnabled('calendar'); diff --git a/apps/calendar/ajax/event/edit.form.php b/apps/calendar/ajax/event/edit.form.php index 837edbbbf0..98c22eb020 100644 --- a/apps/calendar/ajax/event/edit.form.php +++ b/apps/calendar/ajax/event/edit.form.php @@ -41,13 +41,8 @@ switch($dtstart->getDateType()) { $summary = $vevent->getAsString('SUMMARY'); $location = $vevent->getAsString('LOCATION'); -$categories = $vevent->getAsArray('CATEGORIES'); +$categories = $vevent->getAsString('CATEGORIES'); $description = $vevent->getAsString('DESCRIPTION'); -foreach($categories as $category){ - if (!in_array($category, $category_options)){ - array_unshift($category_options, $category); - } -} $last_modified = $vevent->__get('LAST-MODIFIED'); if ($last_modified){ $lastmodified = $last_modified->getDateTime()->format('U'); @@ -189,7 +184,6 @@ if($data['repeating'] == 1){ } $calendar_options = OC_Calendar_Calendar::allCalendars(OC_User::getUser()); -$category_options = OC_Calendar_App::getCategoryOptions(); $repeat_options = OC_Calendar_App::getRepeatOptions(); $repeat_end_options = OC_Calendar_App::getEndOptions(); $repeat_month_options = OC_Calendar_App::getMonthOptions(); @@ -205,7 +199,6 @@ $tmpl = new OC_Template('calendar', 'part.editevent'); $tmpl->assign('id', $id); $tmpl->assign('lastmodified', $lastmodified); $tmpl->assign('calendar_options', $calendar_options); -$tmpl->assign('category_options', $category_options); $tmpl->assign('repeat_options', $repeat_options); $tmpl->assign('repeat_month_options', $repeat_month_options); $tmpl->assign('repeat_weekly_options', $repeat_weekly_options); @@ -242,7 +235,14 @@ if($repeat['repeat'] != 'doesnotrepeat'){ $tmpl->assign('repeat_bymonthday', $repeat['bymonthday']); $tmpl->assign('repeat_bymonth', $repeat['bymonth']); $tmpl->assign('repeat_byweekno', $repeat['byweekno']); +} else { + $tmpl->assign('repeat_month', 'monthday'); + $tmpl->assign('repeat_weekdays', array()); + $tmpl->assign('repeat_interval', 1); + $tmpl->assign('repeat_end', 'never'); + $tmpl->assign('repeat_count', '10'); + $tmpl->assign('repeat_weekofmonth', 'auto'); + $tmpl->assign('repeat_date', ''); + $tmpl->assign('repeat_year', 'bydate'); } $tmpl->printpage(); - -?> \ No newline at end of file diff --git a/apps/calendar/ajax/event/new.form.php b/apps/calendar/ajax/event/new.form.php index c19928a727..838002a3a0 100644 --- a/apps/calendar/ajax/event/new.form.php +++ b/apps/calendar/ajax/event/new.form.php @@ -32,7 +32,6 @@ $start->setTimezone(new DateTimeZone($timezone)); $end->setTimezone(new DateTimeZone($timezone)); $calendar_options = OC_Calendar_Calendar::allCalendars(OC_User::getUser()); -$category_options = OC_Calendar_App::getCategoryOptions(); $repeat_options = OC_Calendar_App::getRepeatOptions(); $repeat_end_options = OC_Calendar_App::getEndOptions(); $repeat_month_options = OC_Calendar_App::getMonthOptions(); @@ -46,7 +45,6 @@ $repeat_bymonthday_options = OC_Calendar_App::getByMonthDayOptions(); $tmpl = new OC_Template('calendar', 'part.newevent'); $tmpl->assign('calendar_options', $calendar_options); -$tmpl->assign('category_options', $category_options); $tmpl->assign('repeat_options', $repeat_options); $tmpl->assign('repeat_month_options', $repeat_month_options); $tmpl->assign('repeat_weekly_options', $repeat_weekly_options); @@ -73,4 +71,3 @@ $tmpl->assign('repeat_weekofmonth', 'auto'); $tmpl->assign('repeat_date', ''); $tmpl->assign('repeat_year', 'bydate'); $tmpl->printpage(); -?> diff --git a/apps/calendar/ajax/event/new.php b/apps/calendar/ajax/event/new.php index 59fda79da7..7070bbf05d 100644 --- a/apps/calendar/ajax/event/new.php +++ b/apps/calendar/ajax/event/new.php @@ -8,8 +8,6 @@ require_once('../../../../lib/base.php'); -$l10n = new OC_L10N('calendar'); - OC_JSON::checkLoggedIn(); OC_JSON::checkAppEnabled('calendar'); diff --git a/apps/calendar/ajax/events.php b/apps/calendar/ajax/events.php index 922df90b76..d053df2e4c 100755 --- a/apps/calendar/ajax/events.php +++ b/apps/calendar/ajax/events.php @@ -8,11 +8,13 @@ require_once ('../../../lib/base.php'); require_once('when/When.php'); - +$l = OC_L10N::get('calendar'); +$unnamed = $l->t('unnamed'); function create_return_event($event, $vevent){ $return_event = array(); + global $unnamed; $return_event['id'] = (int)$event['id']; - $return_event['title'] = htmlspecialchars($event['summary']); + $return_event['title'] = htmlspecialchars(($event['summary']!=NULL || $event['summary'] != '')?$event['summary']: $unnamed); $return_event['description'] = isset($vevent->DESCRIPTION)?htmlspecialchars($vevent->DESCRIPTION->value):''; $last_modified = $vevent->__get('LAST-MODIFIED'); if ($last_modified){ diff --git a/apps/calendar/ajax/import/dialog.php b/apps/calendar/ajax/import/dialog.php index 2e00209215..16ec54d14a 100644 --- a/apps/calendar/ajax/import/dialog.php +++ b/apps/calendar/ajax/import/dialog.php @@ -9,7 +9,6 @@ require_once('../../../../lib/base.php'); OC_JSON::checkLoggedIn(); OC_Util::checkAppEnabled('calendar'); -$l10n = new OC_L10N('calendar'); $tmpl = new OC_Template('calendar', 'part.import'); $tmpl->assign('path', $_POST['path']); $tmpl->assign('filename', $_POST['filename']); diff --git a/apps/calendar/ajax/import/import.php b/apps/calendar/ajax/import/import.php index c0797f6e42..d0bdab4f0d 100644 --- a/apps/calendar/ajax/import/import.php +++ b/apps/calendar/ajax/import/import.php @@ -10,7 +10,7 @@ ob_start(); require_once('../../../../lib/base.php'); OC_JSON::checkLoggedIn(); OC_Util::checkAppEnabled('calendar'); -$nl = "\n"; +$nl = "\n\r"; $progressfile = OC::$APPSROOT . '/apps/calendar/import_tmp/' . md5(session_id()) . '.txt'; if(is_writable('import_tmp/')){ $progressfopen = fopen($progressfile, 'w'); diff --git a/apps/calendar/ajax/settings/guesstimezone.php b/apps/calendar/ajax/settings/guesstimezone.php index d45a70e1ce..c02b8d10b8 100755 --- a/apps/calendar/ajax/settings/guesstimezone.php +++ b/apps/calendar/ajax/settings/guesstimezone.php @@ -10,7 +10,7 @@ require_once('../../../../lib/base.php'); OC_JSON::checkLoggedIn(); OC_JSON::checkAppEnabled('calendar'); -$l = new OC_L10N('calendar'); +$l = OC_L10N::get('calendar'); $lat = $_GET['lat']; $lng = $_GET['long']; diff --git a/apps/calendar/ajax/settings/settimezone.php b/apps/calendar/ajax/settings/settimezone.php index c639753fe2..8dda28335f 100644 --- a/apps/calendar/ajax/settings/settimezone.php +++ b/apps/calendar/ajax/settings/settimezone.php @@ -9,7 +9,7 @@ // Init owncloud require_once('../../../../lib/base.php'); -$l=new OC_L10N('calendar'); +$l=OC_L10N::get('calendar'); // Check if we are a user OC_JSON::checkLoggedIn(); diff --git a/apps/calendar/appinfo/app.php b/apps/calendar/appinfo/app.php index f297c4d16d..1fece8077a 100644 --- a/apps/calendar/appinfo/app.php +++ b/apps/calendar/appinfo/app.php @@ -1,5 +1,5 @@ calendar Calendar - 0.2.1 AGPL Georg Ehrke, Bart Visscher, Jakob Sack 2 diff --git a/apps/calendar/appinfo/version b/apps/calendar/appinfo/version new file mode 100644 index 0000000000..7dff5b8921 --- /dev/null +++ b/apps/calendar/appinfo/version @@ -0,0 +1 @@ +0.2.1 \ No newline at end of file diff --git a/apps/calendar/export.php b/apps/calendar/export.php index 9886ad8e8c..2736eec96c 100644 --- a/apps/calendar/export.php +++ b/apps/calendar/export.php @@ -11,7 +11,7 @@ OC_Util::checkLoggedIn(); OC_Util::checkAppEnabled('calendar'); $cal = isset($_GET['calid']) ? $_GET['calid'] : NULL; $event = isset($_GET['eventid']) ? $_GET['eventid'] : NULL; -$nl = "\n"; +$nl = "\n\r"; if(isset($cal)){ $calendar = OC_Calendar_App::getCalendar($cal); $calobjects = OC_Calendar_Object::all($cal); diff --git a/apps/calendar/index.php b/apps/calendar/index.php index c00a4098f7..f964a13ef7 100644 --- a/apps/calendar/index.php +++ b/apps/calendar/index.php @@ -22,6 +22,7 @@ foreach($calendars as $calendar){ $eventSources[] = OC_Calendar_Calendar::getEventSourceInfo($calendar); } OC_Hook::emit('OC_Calendar', 'getSources', array('sources' => &$eventSources)); +$categories = OC_Calendar_App::getCategoryOptions(); //Fix currentview for fullcalendar if(OC_Preferences::getValue(OC_USER::getUser(), 'calendar', 'currentview', 'month') == "oneweekview"){ @@ -45,9 +46,12 @@ OC_Util::addScript('calendar', 'calendar'); OC_Util::addStyle('calendar', 'style'); OC_Util::addScript('', 'jquery.multiselect'); OC_Util::addStyle('', 'jquery.multiselect'); +OC_Util::addScript('contacts','jquery.multi-autocomplete'); +OC_Util::addScript('','oc-vcategories'); OC_App::setActiveNavigationEntry('calendar_index'); $tmpl = new OC_Template('calendar', 'calendar', 'user'); $tmpl->assign('eventSources', $eventSources); +$tmpl->assign('categories', $categories); if(array_key_exists('showevent', $_GET)){ $tmpl->assign('showevent', $_GET['showevent']); } diff --git a/apps/calendar/js/calendar.js b/apps/calendar/js/calendar.js index 907d94edb0..858990fb89 100644 --- a/apps/calendar/js/calendar.js +++ b/apps/calendar/js/calendar.js @@ -32,13 +32,7 @@ Calendar={ $('#totime').timepicker({ showPeriodLabels: false }); - $('#category').multiselect({ - header: false, - noneSelectedText: $('#category').attr('title'), - selectedList: 2, - minWidth:'auto', - classes: 'category', - }); + $('#category').multiple_autocomplete({source: categories}); Calendar.UI.repeat('init'); $('#end').change(function(){ Calendar.UI.repeat('end'); @@ -370,6 +364,11 @@ Calendar={ } $('#'+id).addClass('active'); }, + categoriesChanged:function(newcategories){ + categories = $.map(newcategories, function(v) {return v;}); + console.log('Calendar categories changed to: ' + categories); + $('#category').multiple_autocomplete('option', 'source', categories); + }, Calendar:{ overview:function(){ if($('#choosecalendar_dialog').dialog('isOpen') == true){ @@ -730,6 +729,8 @@ $(document).ready(function(){ loading: Calendar.UI.loading, eventSources: eventSources }); + OCCategories.changed = Calendar.UI.categoriesChanged; + OCCategories.app = 'calendar'; $('#oneweekview_radio').click(function(){ $('#calendar_holder').fullCalendar('changeView', 'agendaWeek'); }); diff --git a/apps/calendar/lib/app.php b/apps/calendar/lib/app.php index 6e92cf67c5..4b481a4f28 100644 --- a/apps/calendar/lib/app.php +++ b/apps/calendar/lib/app.php @@ -9,9 +9,10 @@ /** * This class manages our app actions */ -OC_Calendar_App::$l10n = new OC_L10N('calendar'); +OC_Calendar_App::$l10n = OC_L10N::get('calendar'); class OC_Calendar_App{ public static $l10n; + protected static $categories = null; public static function getCalendar($id){ $calendar = OC_Calendar_Calendar::find( $id ); @@ -54,7 +55,7 @@ class OC_Calendar_App{ } } - public static function getCategoryOptions() + protected static function getDefaultCategories() { return array( self::$l10n->t('Birthday'), @@ -75,6 +76,54 @@ class OC_Calendar_App{ ); } + protected static function getVCategories() { + if (is_null(self::$categories)) { + self::$categories = new OC_VCategories('calendar', null, self::getDefaultCategories()); + } + return self::$categories; + } + + public static function getCategoryOptions() + { + $categories = self::getVCategories()->categories(); + return $categories; + } + + /** + * scan events for categories. + * @param $events VEVENTs to scan. null to check all events for the current user. + */ + public static function scanCategories($events = null) { + if (is_null($events)) { + $calendars = OC_Calendar_Calendar::allCalendars(OC_User::getUser()); + if(count($calendars) > 0) { + $events = array(); + foreach($calendars as $calendar) { + $calendar_events = OC_Calendar_Object::all($calendar['id']); + $events = $events + $calendar_events; + } + } + } + if(is_array($events) && count($events) > 0) { + $vcategories = self::getVCategories(); + $vcategories->delete($vcategories->categories()); + foreach($events as $event) { + $vobject = OC_VObject::parse($event['calendardata']); + if(!is_null($vobject)) { + $vcategories->loadFromVObject($vobject->VEVENT, true); + } + } + } + } + + /** + * check VEvent for new categories. + * @see OC_VCategories::loadFromVObject + */ + public static function loadCategoriesFromVCalendar(OC_VObject $calendar) { + self::getVCategories()->loadFromVObject($calendar->VEVENT, true); + } + public static function getRepeatOptions(){ return OC_Calendar_Object::getRepeatOptions(self::$l10n); } diff --git a/apps/calendar/lib/calendar.php b/apps/calendar/lib/calendar.php index 7eeb004d18..321f4c73ba 100644 --- a/apps/calendar/lib/calendar.php +++ b/apps/calendar/lib/calendar.php @@ -44,10 +44,8 @@ class OC_Calendar_Calendar{ /** * @brief Returns the list of calendars for a specific user. * @param string $uid User ID - * @param boolean $active + * @param boolean $active Only return calendars with this $active state, default(=null) is don't care * @return array - * - * TODO: what is active for? */ public static function allCalendars($uid, $active=null){ $values = array($uid); diff --git a/apps/calendar/lib/object.php b/apps/calendar/lib/object.php index e0c0e83d5d..d5622f6251 100644 --- a/apps/calendar/lib/object.php +++ b/apps/calendar/lib/object.php @@ -93,6 +93,7 @@ class OC_Calendar_Object{ */ public static function add($id,$data){ $object = OC_VObject::parse($data); + OC_Calendar_App::loadCategoriesFromVCalendar($object); list($type,$startdate,$enddate,$summary,$repeating,$uid) = self::extractData($object); if(is_null($uid)){ @@ -139,6 +140,7 @@ class OC_Calendar_Object{ $oldobject = self::find($id); $object = OC_VObject::parse($data); + OC_Calendar_App::loadCategoriesFromVCalendar($object); list($type,$startdate,$enddate,$summary,$repeating,$uid) = self::extractData($object); $stmt = OC_DB::prepare( 'UPDATE *PREFIX*calendar_objects SET objecttype=?,startdate=?,enddate=?,repeating=?,summary=?,calendardata=?, lastmodified = ? WHERE id = ?' ); @@ -320,27 +322,6 @@ class OC_Calendar_Object{ return $dtend; } - public static function getCategoryOptions($l10n) - { - return array( - $l10n->t('Birthday'), - $l10n->t('Business'), - $l10n->t('Call'), - $l10n->t('Clients'), - $l10n->t('Deliverer'), - $l10n->t('Holidays'), - $l10n->t('Ideas'), - $l10n->t('Journey'), - $l10n->t('Jubilee'), - $l10n->t('Meeting'), - $l10n->t('Other'), - $l10n->t('Personal'), - $l10n->t('Projects'), - $l10n->t('Questions'), - $l10n->t('Work'), - ); - } - public static function getRepeatOptions($l10n) { return array( @@ -457,10 +438,6 @@ class OC_Calendar_Object{ $errnum++; } - if(isset($request['categories']) && !is_array($request['categories'])){ - $errors['categories'] = $l10n->t('Not an array'); - } - $fromday = substr($request['from'], 0, 2); $frommonth = substr($request['from'], 3, 2); $fromyear = substr($request['from'], 6, 4); @@ -628,7 +605,7 @@ class OC_Calendar_Object{ { $title = $request["title"]; $location = $request["location"]; - $categories = isset($request["categories"]) ? $request["categories"] : array(); + $categories = $request["categories"]; $allday = isset($request["allday"]); $from = $request["from"]; $to = $request["to"]; @@ -802,7 +779,7 @@ class OC_Calendar_Object{ $vevent->setString('LOCATION', $location); $vevent->setString('DESCRIPTION', $description); - $vevent->setString('CATEGORIES', join(',', $categories)); + $vevent->setString('CATEGORIES', $categories); /*if($repeat == "true"){ $vevent->RRULE = $repeat; diff --git a/apps/calendar/lib/search.php b/apps/calendar/lib/search.php index 8405866392..da5fa35bc2 100644 --- a/apps/calendar/lib/search.php +++ b/apps/calendar/lib/search.php @@ -1,6 +1,6 @@ var defaultView = ''; var eventSources = ; + var categories = ; var dayNames = tA(array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'))) ?>; var dayNamesShort = tA(array('Sun.', 'Mon.', 'Tue.', 'Wed.', 'Thu.', 'Fri.', 'Sat.'))) ?>; var monthNames = tA(array('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'))) ?>; diff --git a/apps/calendar/templates/part.eventform.php b/apps/calendar/templates/part.eventform.php index 8a8f420f84..3830c273a7 100644 --- a/apps/calendar/templates/part.eventform.php +++ b/apps/calendar/templates/part.eventform.php @@ -10,12 +10,8 @@ t("Category");?>: - + + <?php echo $l->t('Edit categories'); ?> 1) { ?>    t("Calendar");?>: diff --git a/apps/calendar/templates/settings.php b/apps/calendar/templates/settings.php index f74a45203e..fb2a04a649 100644 --- a/apps/calendar/templates/settings.php +++ b/apps/calendar/templates/settings.php @@ -31,7 +31,7 @@ - " name="timeformat"> diff --git a/apps/contacts/ajax/addcontact.php b/apps/contacts/ajax/addcontact.php index 839a391998..68da54655a 100644 --- a/apps/contacts/ajax/addcontact.php +++ b/apps/contacts/ajax/addcontact.php @@ -39,13 +39,14 @@ foreach ($_POST as $key=>$element) { debug('_POST: '.$key.'=>'.$element); } -$aid = $_POST['aid']; +$aid = isset($_POST['aid'])?$_POST['aid']:null; +if(!$aid) { + $aid = min(OC_Contacts_Addressbook::activeIds()); // first active addressbook. +} OC_Contacts_App::getAddressbook( $aid ); // is owner access check $fn = trim($_POST['fn']); $n = trim($_POST['n']); -debug('N: '.$n); -debug('FN: '.$fn); $vcard = new OC_VObject('VCARD'); $vcard->setUID(); diff --git a/apps/contacts/ajax/addproperty.php b/apps/contacts/ajax/addproperty.php index e1a3129283..d2c0291e8c 100644 --- a/apps/contacts/ajax/addproperty.php +++ b/apps/contacts/ajax/addproperty.php @@ -122,13 +122,4 @@ if(!OC_Contacts_VCard::edit($id,$vcard)) { exit(); } -$adr_types = OC_Contacts_App::getTypesOfProperty('ADR'); -$phone_types = OC_Contacts_App::getTypesOfProperty('TEL'); - -$tmpl = new OC_Template('contacts','part.property'); -$tmpl->assign('adr_types',$adr_types); -$tmpl->assign('phone_types',$phone_types); -$tmpl->assign('property',OC_Contacts_VCard::structureProperty($property,$line)); -$page = $tmpl->fetchPage(); - -OC_JSON::success(array('data' => array( 'checksum' => $checksum, 'page' => $page ))); +OC_JSON::success(array('data' => array( 'checksum' => $checksum ))); diff --git a/apps/contacts/ajax/categories/list.php b/apps/contacts/ajax/categories/list.php index 3b41b7bfa9..64d74c82e6 100644 --- a/apps/contacts/ajax/categories/list.php +++ b/apps/contacts/ajax/categories/list.php @@ -10,7 +10,7 @@ require_once('../../../../lib/base.php'); OC_JSON::checkLoggedIn(); OC_JSON::checkAppEnabled('contacts'); -$categories = OC_Contacts_App::$categories->categories(); +$categories = OC_Contacts_App::getCategories(); OC_JSON::success(array('data' => array('categories'=>$categories))); diff --git a/apps/contacts/ajax/categories/rescan.php b/apps/contacts/ajax/categories/rescan.php index dd27192baa..5f1057bab4 100644 --- a/apps/contacts/ajax/categories/rescan.php +++ b/apps/contacts/ajax/categories/rescan.php @@ -36,13 +36,8 @@ if(count($contacts) == 0) { bailOut(OC_Contacts_App::$l10n->t('No contacts found.')); } -$cards = array(); -foreach($contacts as $contact) { - $cards[] = $contact['carddata']; -} - -OC_Contacts_App::$categories->rescan($cards); -$categories = OC_Contacts_App::$categories->categories(); +OC_Contacts_App::scanCategories($contacts); +$categories = OC_Contacts_App::getCategories(); OC_JSON::success(array('data' => array('categories'=>$categories))); diff --git a/apps/contacts/ajax/deletecard.php b/apps/contacts/ajax/deletecard.php index e26dfd6ebf..5675aef5f1 100644 --- a/apps/contacts/ajax/deletecard.php +++ b/apps/contacts/ajax/deletecard.php @@ -19,6 +19,11 @@ * License along with this library. If not, see . * */ +function bailOut($msg) { + OC_JSON::error(array('data' => array('message' => $msg))); + OC_Log::write('contacts','ajax/saveproperty.php: '.$msg, OC_Log::DEBUG); + exit(); +} // Init owncloud require_once('../../../lib/base.php'); @@ -27,7 +32,10 @@ require_once('../../../lib/base.php'); OC_JSON::checkLoggedIn(); OC_JSON::checkAppEnabled('contacts'); -$id = $_GET['id']; +$id = isset($_GET['id'])?$_GET['id']:null; +if(!$id) { + bailOut(OC_Contacts_App::$l10n->t('id is not set.')); +} $card = OC_Contacts_App::getContactObject( $id ); OC_Contacts_VCard::delete($id); diff --git a/apps/contacts/ajax/saveproperty.php b/apps/contacts/ajax/saveproperty.php index 95a7ac2019..99d55e7927 100644 --- a/apps/contacts/ajax/saveproperty.php +++ b/apps/contacts/ajax/saveproperty.php @@ -35,9 +35,9 @@ function bailOut($msg) { function debug($msg) { OC_Log::write('contacts','ajax/saveproperty.php: '.$msg, OC_Log::DEBUG); } -foreach ($_POST as $key=>$element) { - debug('_POST: '.$key.'=>'.print_r($element, true)); -} +// foreach ($_POST as $key=>$element) { +// debug('_POST: '.$key.'=>'.print_r($element, true)); +// } $id = isset($_POST['id'])?$_POST['id']:null; $name = isset($_POST['name'])?$_POST['name']:null; @@ -83,32 +83,53 @@ if($element != $name) { bailOut(OC_Contacts_App::$l10n->t('Something went FUBAR. ').$name.' != '.$element); } +/* preprocessing value */ switch($element) { case 'BDAY': $date = New DateTime($value); //$vcard->setDateTime('BDAY', $date, Sabre_VObject_Element_DateTime::DATE); $value = $date->format(DateTime::ATOM); + break; case 'FN': if(!$value) { // create a method thats returns an alternative for FN. //$value = getOtherValue(); } - case 'N': - case 'ORG': - case 'NOTE': - case 'NICKNAME': - case 'CATEGORIES': - debug('Setting string:'.$name.' '.$value); - $vcard->setString($name, $value); break; + //case 'CATEGORIES': + /* multi autocomplete triggers an save with empty value + if (!$value) { + $value = $vcard->getAsString('CATEGORIES'); + } + break;*/ case 'EMAIL': $value = strtolower($value); - case 'TEL': - case 'ADR': // should I delete the property if empty or throw an error? - debug('Setting element: (EMAIL/TEL/ADR)'.$element); - if(!$value) { - unset($vcard->children[$line]); // Should never happen... - } else { + break; +} + +if(!$value) { + unset($vcard->children[$line]); + $checksum = ''; +} else { + /* setting value */ + switch($element) { + case 'BDAY': + case 'FN': + case 'N': + case 'ORG': + case 'NOTE': + case 'NICKNAME': + debug('Setting string:'.$name.' '.$value); + $vcard->setString($name, $value); + break; + case 'CATEGORIES': + debug('Setting string:'.$name.' '.$value); + $vcard->children[$line]->setValue($value); + break; + case 'EMAIL': + case 'TEL': + case 'ADR': // should I delete the property if empty or throw an error? + debug('Setting element: (EMAIL/TEL/ADR)'.$element); $vcard->children[$line]->setValue($value); $vcard->children[$line]->parameters = array(); if(!is_null($parameters)) { @@ -121,12 +142,12 @@ switch($element) { } } } - } - break; + break; + } + // Do checksum and be happy + $checksum = md5($vcard->children[$line]->serialize()); } -// Do checksum and be happy -$checksum = md5($vcard->children[$line]->serialize()); -debug('New checksum: '.$checksum); +//debug('New checksum: '.$checksum); if(!OC_Contacts_VCard::edit($id,$vcard)) { bailOut(OC_Contacts_App::$l10n->t('Error updating contact property.')); diff --git a/apps/contacts/appinfo/app.php b/apps/contacts/appinfo/app.php index 85c383c4c3..0ff8fd29ef 100644 --- a/apps/contacts/appinfo/app.php +++ b/apps/contacts/appinfo/app.php @@ -20,7 +20,7 @@ OC_App::addNavigationEntry( array( 'order' => 10, 'href' => OC_Helper::linkTo( 'contacts', 'index.php' ), 'icon' => OC_Helper::imagePath( 'settings', 'users.svg' ), - 'name' => OC_Contacts_App::$l10n->t('Contacts') )); + 'name' => OC_L10N::get('contact')->t('Contacts') )); OC_APP::registerPersonal('contacts','settings'); diff --git a/apps/contacts/appinfo/info.xml b/apps/contacts/appinfo/info.xml index 0e2b133600..55ddf42ccc 100644 --- a/apps/contacts/appinfo/info.xml +++ b/apps/contacts/appinfo/info.xml @@ -2,7 +2,6 @@ contacts Contacts - 0.1 AGPL Jakob Sack 2 diff --git a/apps/contacts/appinfo/migrate.php b/apps/contacts/appinfo/migrate.php new file mode 100644 index 0000000000..a6c6bc20fa --- /dev/null +++ b/apps/contacts/appinfo/migrate.php @@ -0,0 +1,68 @@ +'contacts_addressbooks', + 'matchcol'=>'userid', + 'matchval'=>$this->uid, + 'idcol'=>'id' + ); + $ids = $this->content->copyRows( $options ); + + $options = array( + 'table'=>'contacts_cards', + 'matchcol'=>'addressbookid', + 'matchval'=>$ids + ); + + // Export tags + $ids2 = $this->content->copyRows( $options ); + + // If both returned some ids then they worked + if( is_array( $ids ) && is_array( $ids2 ) ) + { + return true; + } else { + return false; + } + + } + + // Import function for bookmarks + function import( ){ + switch( $this->appinfo->version ){ + default: + // All versions of the app have had the same db structure, so all can use the same import function + $query = $this->content->prepare( "SELECT * FROM contacts_addressbooks WHERE userid LIKE ?" ); + $results = $query->execute( array( $this->olduid ) ); + $idmap = array(); + while( $row = $results->fetchRow() ){ + // Import each bookmark, saving its id into the map + $query = OC_DB::prepare( "INSERT INTO *PREFIX*contacts_addressbooks (`userid`, `displayname`, `uri`, `description`, `ctag`) VALUES (?, ?, ?, ?, ?)" ); + $query->execute( array( $this->uid, $row['displayname'], $row['uri'], $row['description'], $row['ctag'] ) ); + // Map the id + $idmap[$row['id']] = OC_DB::insertid(); + } + // Now tags + foreach($idmap as $oldid => $newid){ + $query = $this->content->prepare( "SELECT * FROM contacts_cards WHERE addressbookid LIKE ?" ); + $results = $query->execute( array( $oldid ) ); + while( $row = $results->fetchRow() ){ + // Import the tags for this bookmark, using the new bookmark id + $query = OC_DB::prepare( "INSERT INTO *PREFIX*contacts_cards (`addressbookid`, `fullname`, `carddata`, `uri`, `lastmodified`) VALUES (?, ?, ?, ?, ?)" ); + $query->execute( array( $newid, $row['fullname'], $row['carddata'], $row['uri'], $row['lastmodified'] ) ); + } + } + // All done! + break; + } + + return true; + } + +} + +// Load the provider +new OC_Migration_Provider_Contacts( 'contacts' ); \ No newline at end of file diff --git a/apps/contacts/appinfo/version b/apps/contacts/appinfo/version new file mode 100644 index 0000000000..ceab6e11ec --- /dev/null +++ b/apps/contacts/appinfo/version @@ -0,0 +1 @@ +0.1 \ No newline at end of file diff --git a/apps/contacts/css/contacts.css b/apps/contacts/css/contacts.css index 7c36a511d6..2d20794384 100644 --- a/apps/contacts/css/contacts.css +++ b/apps/contacts/css/contacts.css @@ -10,69 +10,29 @@ #contacts_propertymenu_button { position:absolute;top:15px;right:150px; background:url('../../../core/img/actions/add.svg') no-repeat center; } #contacts_propertymenu { background-color: #fff; position:absolute;top:40px;right:150px; overflow:hidden; text-overflow:ellipsis; /*border: thin solid #1d2d44;*/ -moz-box-shadow:0 0 10px #000; -webkit-box-shadow:0 0 10px #000; box-shadow:0 0 10px #000; -moz-border-radius:0.5em; -webkit-border-radius:0.5em; border-radius:0.5em; -moz-border-radius:0.5em; -webkit-border-radius:0.5em; border-radius:0.5em; } #contacts_propertymenu li { display: block; font-weight: bold; height: 20px; width: 100px; } -/*#contacts_propertymenu li:first-child { border-top: thin solid #1d2d44; -moz-border-radius-topleft:0.5em; -webkit-border-top-left-radius:0.5em; border-top-left-radius:0.5em; -moz-border-radius-topright:0.5em; -webkit-border-top-right-radius:0.5em; border-top-right-radius:0.5em; } -#contacts_propertymenu li:last-child { border-bottom: thin solid #1d2d44; -moz-border-radius-bottomleft:0.5em; -webkit-border-bottom-left-radius:0.5em; border-bottom-left-radius:0.5em; -moz-border-radius-bottomright:0.5em; -webkit-border-bottom-right-radius:0.5em; border-bottom-right-radius:0.5em; }*/ #contacts_propertymenu li a { padding: 3px; display: block } #contacts_propertymenu li:hover { background-color: #1d2d44; } #contacts_propertymenu li a:hover { color: #fff } -#actionbar { height: 30px; width: 200px; position: fixed; right: 0px; top: 75px; margin: 0 0 0 0; padding: 0 0 0 0;} -#card { /*max-width: 70em; border: thin solid lightgray; display: block;*/ } +#actionbar { height: 30px; width: 200px; position: fixed; right: 0px; top: 75px; margin: 0 0 0 0; padding: 0 0 0 0; z-index: 1000; } +#card { width: auto;/*max-width: 70em; border: thin solid lightgray; display: block;*/ } #firstrun { width: 100%; position: absolute; top: 5em; left: 0; text-align: center; font-weight:bold; font-size:1.5em; color:#777; } #firstrun #selections { font-size:0.8em; margin: 2em auto auto auto; clear: both; } -#card input[type="text"].contacts_property,input[type="email"].contacts_property { width: 14em; } +#card input[type="text"].contacts_property,input[type="email"].contacts_property { width: 14em; float: left; } .categories { float: left; width: 16em; } -#card input[type="text"],input[type="email"],input[type="tel"],input[type="date"], select { background-color: #f8f8f8; border: 0 !important; -webkit-appearance:none !important; -moz-appearance:none !important; -webkit-box-sizing:none !important; -moz-box-sizing:none !important; box-sizing:none !important; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; -moz-border-radius: 0px; -webkit-border-radius: 0px; border-radius: 0px; float: left; } -#card input[type="text"]:hover, input[type="text"]:focus, input[type="text"]:active,input[type="email"]:hover,input[type="tel"]:hover,input[type="date"]:hover,input[type="date"],input[type="date"]:hover,input[type="date"]:active,input[type="date"]:active,input[type="date"]:active,input[type="email"]:active,input[type="tel"]:active, select:hover, select:focus, select:active { border: 0 !important; -webkit-appearance:textfield; -moz-appearance:textfield; -webkit-box-sizing:content-box; -moz-box-sizing:content-box; box-sizing:content-box; background:#fff; color:#333; border:1px solid #ddd; -moz-box-shadow:0 1px 1px #fff, 0 2px 0 #bbb inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; outline:none; float: left; } -input[type="text"]:invalid,input[type="email"]:invalid,input[type="tel"]:invalid,input[type="date"]:invalid { background-color: #ffc0c0 !important; } -/*input[type="text"]:valid,input[type="email"]:valid,input[type="tel"]:valid,input[type="date"]:valid { background-color: #b1d28f !important; }*/ -dl.form -{ - width: 100%; - float: left; - clear: right; - margin: 0; - padding: 0; -} - -.form dt -{ - display: table-cell; - clear: left; - float: left; - width: 7em; - /*overflow: hidden;*/ - margin: 0; - padding: 0.8em 0.5em 0 0; - font-weight: bold; - text-align:right; - text-overflow:ellipsis; - o-text-overflow: ellipsis; - vertical-align: text-bottom; - /* - white-space: pre-wrap; - white-space: -moz-pre-wrap !important; - white-space: -pre-wrap; - white-space: -o-pre-wrap;*/ -} - -.form dd -{ - display: table-cell; - clear: right; - float: left; - margin: 0; - padding: 0px; - white-space: nowrap; - vertical-align: text-bottom; - /*min-width: 20em;*/ - /*background-color: yellow;*/ -} +#card input[type="text"],input[type="email"],input[type="tel"],input[type="date"], select, textarea { background-color: #fefefe; border: 0 !important; -webkit-appearance:none !important; -moz-appearance:none !important; -webkit-box-sizing:none !important; -moz-box-sizing:none !important; box-sizing:none !important; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; -moz-border-radius: 0px; -webkit-border-radius: 0px; border-radius: 0px; float: left; } +#card input[type="text"]:hover, input[type="text"]:focus, input[type="text"]:active,input[type="email"]:hover,input[type="tel"]:hover,input[type="date"]:hover,input[type="date"],input[type="date"]:hover,input[type="date"]:active,input[type="date"]:active,input[type="date"]:active,input[type="email"]:active,input[type="tel"]:active, select:hover, select:focus, select:active, textarea:focus, textarea:hover { border: 0 !important; -webkit-appearance:textfield; -moz-appearance:textfield; -webkit-box-sizing:content-box; -moz-box-sizing:content-box; box-sizing:content-box; background:#fff; color:#333; border:1px solid #ddd; -moz-box-shadow:0 1px 1px #fff, 0 2px 0 #bbb inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; outline:none; float: left; } +input[type="text"]:invalid,input[type="email"]:invalid,input[type="tel"]:invalid,input[type="date"]:invalid, textarea:invalid { color: #bbb !important; } +textarea { min-height: 4em; } +dl.form { width: 100%; float: left; clear: right; margin: 0; padding: 0; } +.form dt { display: table-cell; clear: left; float: left; width: 7em; margin: 0; padding: 0.8em 0.5em 0 0; text-align:right; text-overflow:ellipsis; o-text-overflow: ellipsis; vertical-align: text-bottom; color: #bbb;/* white-space: pre-wrap; white-space: -moz-pre-wrap !important; white-space: -pre-wrap; white-space: -o-pre-wrap;*/ } +.form dd { display: table-cell; clear: right; float: left; margin: 0; padding: 0px; white-space: nowrap; vertical-align: text-bottom; } +#address.form dt { min-width: 5em; } +#address.form dl { min-width: 10em; } .loading { background: url('../../../core/img/loading.gif') no-repeat center !important; /*cursor: progress; */ cursor: wait; } - -/*.add { cursor: pointer; width: 25px; height: 25px; margin: 0px; float: right; position:relative; content: "\+"; font-weight: bold; color: #666; font-size: large; bottom: 0px; right: 0px; clear: both; text-align: center; vertical-align: bottom; display: none; }*/ - +.ui-autocomplete-loading { background: url('../../../core/img/loading.gif') right center no-repeat; } +.float { float: left; } .listactions { height: 1em; width:60px; float: left; clear: right; } .add,.edit,.delete,.mail, .globe { cursor: pointer; width: 20px; height: 20px; margin: 0; float: left; position:relative; display: none; } .add { background:url('../../../core/img/actions/add.svg') no-repeat center; clear: both; } @@ -82,77 +42,23 @@ dl.form /*.globe { background:url('../img/globe.svg') no-repeat center; }*/ .globe { background:url('../../../core/img/actions/public.svg') no-repeat center; } -#messagebox_msg { font-weight: bold; font-size: 1.2em; } - -/* Name editor */ -#edit_name_dialog { - /*width: 25em;*/ - padding:0; -} -#edit_name_dialog > input { - width: 15em; -} -/* Address editor */ -#edit_address_dialog { - /*width: 30em;*/ -} -#edit_address_dialog > input { - width: 15em; -} +#edit_name_dialog { padding:0; } +#edit_name_dialog > input { width: 15em; } +#edit_address_dialog { /*width: 30em;*/ } +#edit_address_dialog > input { width: 15em; } #edit_photo_dialog_img { display: block; width: 150; height: 200; border: thin solid black; } -#fn { float: left; } -/** - * Create classes form, floateven and floatodd which flows left and right respectively. - */ -.contactsection { - float: left; - min-width: 30em; - max-width: 40em; - margin: 0.5em; - border: thin solid lightgray; - -webkit-border-radius: 0.5em; - -moz-border-radius: 0.5em; - border-radius: 0.5em; - background-color: #f8f8f8; -} +#fn { float: left !important; width: 18em !important; } +#name { /*position: absolute; top: 0px; left: 0px;*/ min-width: 25em; height: 2em; clear: right; display: block; } +#identityprops { /*position: absolute; top: 2.5em; left: 0px;*/ } +/*#contact_photo { max-width: 250px; }*/ +#contact_identity { min-width: 30em; } +.contactsection { position: relative; float: left; /*max-width: 40em;*/ padding: 0.5em; height: auto: border: thin solid lightgray;/* -webkit-border-radius: 0.5em; -moz-border-radius: 0.5em; border-radius: 0.5em; background-color: #f8f8f8;*/ } -.contactpart legend { - /*background: #fff; - font-weight: bold; - left: 1em; - border: thin solid gray; - -webkit-border-radius: 0.5em; - -moz-border-radius: 0.5em; - border-radius: 0.5em; - padding: 3px;*/ -width:auto; padding:.3em; border:1px solid #ddd; font-weight:bold; cursor:pointer; background:#f8f8f8; color:#555; text-shadow:#fff 0 1px 0; -moz-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; -} -/*#contacts_details_photo { - cursor: pointer; - z-index:1; - margin: auto; -} -*/ -#cropbox { - margin: auto; -} - -/* Photo editor */ -/*#contacts_details_photo_wrapper { - z-index: 1000; -}*/ -#contacts_details_photo { - border-radius: 0.5em; - border: thin solid #bbb; - padding: 0.5em; - margin: 1em 1em 1em 7em; - cursor: pointer; - /*background: #f8f8f8;*/ - background: url(../../../core/img/loading.gif) no-repeat center center; - clear: right; -} +.contactpart legend { width:auto; padding:.3em; border:1px solid #ddd; font-weight:bold; cursor:pointer; background:#f8f8f8; color:#555; text-shadow:#fff 0 1px 0; -moz-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; } +#cropbox { margin: auto; } +#contacts_details_photo { border-radius: 0.5em; border: thin solid #bbb; margin: 0.3em; cursor: pointer; background: url(../../../core/img/loading.gif) no-repeat center center; display: block; /* clear: right;*/ } #contacts_details_photo:hover { background: #fff; } -#contacts_details_photo_progress { margin: 0.3em 0.3em 0.3em 7em; clear: left; } +/*#contacts_details_photo_progress { margin: 0.3em 0.3em 0.3em 7em; clear: left; }*/ /* Address editor */ #addressdisplay { padding: 0.5em; } dl.addresscard { background-color: #fff; float: left; width: 45%; margin: 0 0.3em 0.3em 0.3em; padding: 0; border: thin solid lightgray; } @@ -168,17 +74,12 @@ dl.addresscard dd > ul { margin: 0.3em; padding: 0.3em; } #adr_zipcode {} #adr_country {} -.delimiter { - height: 10px; - clear: both; -} - -/*input[type="text"] { float: left; max-width: 15em; } -input[type="radio"] { float: left; -khtml-appearance: none; width: 20px; height: 20px; vertical-align: middle; }*/ #file_upload_target, #crop_target { display:none; } -#file_upload_start { opacity:0; filter:alpha(opacity=0); z-index:1; position:absolute; left:0; top:0; cursor:pointer; width:0; height:0;} +#file_upload_start { opacity:0; filter:alpha(opacity=0); z-index:1; /*position:absolute; left:0; top:0;*/ cursor:pointer; width:0; height:0;} input[type="checkbox"] { width: 20px; height: 20px; vertical-align: bottom; } +.big { font-weight:bold; font-size:1.2em; } +.huge { font-weight:bold; font-size:1.5em; } .propertycontainer dd { float: left; width: 25em; } .propertylist { clear: none; max-width: 28em; } .propertylist li { /*background-color: cyan; */ min-width: 25em; /*max-width: 30em;*/ display: block; clear: right; } diff --git a/apps/contacts/img/person_large.png b/apps/contacts/img/person_large.png index f57511e100..4edba0c548 100644 Binary files a/apps/contacts/img/person_large.png and b/apps/contacts/img/person_large.png differ diff --git a/apps/contacts/index.php b/apps/contacts/index.php index 04f6c65a14..4039b8afd3 100644 --- a/apps/contacts/index.php +++ b/apps/contacts/index.php @@ -34,7 +34,7 @@ if(!is_null($id)) { } $property_types = OC_Contacts_App::getAddPropertyOptions(); $phone_types = OC_Contacts_App::getTypesOfProperty('TEL'); -$categories = OC_Contacts_App::$categories->categories(); +$categories = OC_Contacts_App::getCategories(); $upload_max_filesize = OC_Helper::computerFileSize(ini_get('upload_max_filesize')); $post_max_size = OC_Helper::computerFileSize(ini_get('post_max_size')); @@ -52,7 +52,6 @@ OC_Util::addScript('contacts','jquery.inview'); OC_Util::addScript('contacts','jquery.Jcrop'); OC_Util::addScript('contacts','jquery.multi-autocomplete'); OC_Util::addStyle('','jquery.multiselect'); -OC_Util::addStyle('','oc-vcategories'); OC_Util::addStyle('contacts','jquery.combobox'); OC_Util::addStyle('contacts','jquery.Jcrop'); OC_Util::addStyle('contacts','contacts'); @@ -60,10 +59,9 @@ OC_Util::addStyle('contacts','contacts'); $tmpl = new OC_Template( "contacts", "index", "user" ); $tmpl->assign('uploadMaxFilesize', $maxUploadFilesize); $tmpl->assign('uploadMaxHumanFilesize', OC_Helper::humanFileSize($maxUploadFilesize)); -$tmpl->assign('property_types',$property_types); -$tmpl->assign('categories',OC_Contacts_App::getCategories()); -$tmpl->assign('phone_types',$phone_types); -$tmpl->assign('categories',$categories); +$tmpl->assign('property_types', $property_types); +$tmpl->assign('phone_types', $phone_types); +$tmpl->assign('categories', $categories); $tmpl->assign('addressbooks', $addressbooks); $tmpl->assign('contacts', $contacts); $tmpl->assign('details', $details ); diff --git a/apps/contacts/js/contacts.js b/apps/contacts/js/contacts.js index 18214cb1cc..5f2bd6e9df 100644 --- a/apps/contacts/js/contacts.js +++ b/apps/contacts/js/contacts.js @@ -65,7 +65,7 @@ Contacts={ propertyTypeFor:function(obj) { return $(obj).parents('.propertycontainer').first().data('element'); }, - showHideContactInfo:function() { + /*showHideContactInfo:function() { var show = ($('#emaillist li.propertycontainer').length > 0 || $('#phonelist li.propertycontainer').length > 0 || $('#addressdisplay dl.propertycontainer').length > 0); console.log('showHideContactInfo: ' + show); if(show) { @@ -73,8 +73,8 @@ Contacts={ } else { $('#contact_communication').hide(); } - }, - checkListFor:function(obj) { + },*/ + /*checkListFor:function(obj) { var type = $(obj).parents('.propertycontainer').first().data('element'); console.log('checkListFor: ' + type); switch (type) { @@ -101,7 +101,7 @@ Contacts={ case 'BDAY': break; } - }, + },*/ loading:function(obj, state) { if(state) { $(obj).addClass('loading'); @@ -116,7 +116,7 @@ Contacts={ }, loadListHandlers:function() { //$('.add,.delete').hide(); - $('.globe,.mail,.delete,.edit').tipsy(); + $('.globe,.mail,.delete,.edit,.tip').tipsy(); $('.addresscard,.propertylist li,.propertycontainer').hover( function () { $(this).find('.globe,.mail,.delete,.edit').fadeIn(100); @@ -137,18 +137,14 @@ Contacts={ $(this).find('.add').fadeOut(500); } );*/ - $('.button,.action').tipsy(); - $('#contacts_deletecard').tipsy({gravity: 'ne'}); - $('#contacts_downloadcard').tipsy({gravity: 'ne'}); //$('#fn').jec(); $('#fn_select').combobox({ 'id': 'fn', 'name': 'value', - 'classes': ['contacts_property'], + 'classes': ['contacts_property', 'huge', 'tip', 'float'], + 'attributes': {'placeholder': t('contacts', 'Enter name')}, 'title': t('contacts', 'Format custom, Short name, Full name, Reverse or Reverse with comma')}); //$('.jecEditableOption').attr('title', t('contacts','Custom')); - $('#fn').tipsy(); - $('#contacts_details_photo_wrapper').tipsy(); $('#bday').datepicker({ dateFormat : 'dd-mm-yy' }); @@ -175,10 +171,6 @@ Contacts={ // Contacts.UI.Card.editAddress(); // return false; // }); - $('#n').click(function(){ - Contacts.UI.Card.editName(); - //return false; - }); $('#edit_name').click(function(){ Contacts.UI.Card.editName(); return false; @@ -200,6 +192,9 @@ Contacts={ } ] ); $('#categories').multiple_autocomplete({source: categories}); + $('.button,.action,.tip').tipsy(); + $('#contacts_deletecard').tipsy({gravity: 'ne'}); + $('#contacts_downloadcard').tipsy({gravity: 'ne'}); Contacts.UI.loadListHandlers(); }, Card:{ @@ -213,19 +208,27 @@ Contacts={ honpre:'', honsuf:'', data:undefined, - update:function() { + update:function(id) { // Make sure proper DOM is loaded. - console.log('Card.update(), #n: ' + $('#n').length); + var newid; + console.log('Card.update(), id: ' + id); console.log('Card.update(), #contacts: ' + $('#contacts li').length); - if($('#n').length == 0 && $('#contacts li').length > 0) { + if(id == undefined) { + newid = $('#contacts li:first-child').data('id'); + } else { + newid = id; + } + if($('#contacts li').length > 0) { $.getJSON(OC.filePath('contacts', 'ajax', 'loadcard.php'),{},function(jsondata){ if(jsondata.status == 'success'){ $('#rightcontent').html(jsondata.data.page); Contacts.UI.loadHandlers(); if($('#contacts li').length > 0) { - var firstid = $('#contacts li:first-child').data('id'); - console.log('trying to load: ' + firstid); - $.getJSON(OC.filePath('contacts', 'ajax', 'contactdetails.php'),{'id':firstid},function(jsondata){ + //var newid = $('#contacts li:first-child').data('id'); + //$('#contacts li:first-child').addClass('active'); + $('#leftcontent li[data-id="'+newid+'"]').addClass('active'); + console.log('trying to load: ' + newid); + $.getJSON(OC.filePath('contacts', 'ajax', 'contactdetails.php'),{'id':newid},function(jsondata){ if(jsondata.status == 'success'){ Contacts.UI.Card.loadContact(jsondata.data); } else{ @@ -251,16 +254,28 @@ Contacts={ }); } }, - export:function() { + doExport:function() { document.location.href = OC.linkTo('contacts', 'export.php') + '?contactid=' + this.id; //$.get(OC.linkTo('contacts', 'export.php'),{'contactid':this.id},function(jsondata){ //}); }, - import:function(){ + doImport:function(){ Contacts.UI.notImplemented(); }, - add:function(n, fn, aid){ // add a new contact + add:function(n, fn, aid, isnew){ // add a new contact console.log('Add contact: ' + n + ', ' + fn + ' ' + aid); + var card = $('#card')[0]; + if(!card) { + console.log('Loading proper card DOM'); + $.getJSON(OC.filePath('contacts', 'ajax', 'loadcard.php'),{},function(jsondata){ + if(jsondata.status == 'success'){ + $('#rightcontent').html(jsondata.data.page); + Contacts.UI.loadHandlers(); + } else{ + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); + } + }); + } $.post(OC.filePath('contacts', 'ajax', 'addcontact.php'), { n: n, fn: fn, aid: aid }, function(jsondata) { if (jsondata.status == 'success'){ @@ -283,7 +298,15 @@ Contacts={ if(!added) { $('#leftcontent ul').append(item); } - + if(isnew) { + Contacts.UI.Card.addProperty('EMAIL'); + Contacts.UI.Card.addProperty('TEL'); + Contacts.UI.Card.addProperty('NICKNAME'); + Contacts.UI.Card.addProperty('ORG'); + Contacts.UI.Card.addProperty('CATEGORIES'); + $('#fn').focus(); + $('#fn').select(); + } } else{ OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); @@ -300,41 +323,55 @@ Contacts={ } }); }, - delete: function() { + doDelete:function() { $('#contacts_deletecard').tipsy('hide'); - $.getJSON('ajax/deletecard.php',{'id':this.id},function(jsondata){ - if(jsondata.status == 'success'){ - $('#leftcontent [data-id="'+jsondata.data.id+'"]').remove(); - $('#rightcontent').data('id',''); - //$('#rightcontent').empty(); - this.id = this.fn = this.fullname = this.shortname = this.famname = this.givname = this.addname = this.honpre = this.honsuf = ''; - this.data = undefined; - // Load first in list. - if($('#contacts li').length > 0) { - Contacts.UI.Card.update(); - } else { - // load intro page - $.getJSON('ajax/loadintro.php',{},function(jsondata){ - if(jsondata.status == 'success'){ - id = ''; - $('#rightcontent').data('id',''); - $('#rightcontent').html(jsondata.data.page); + OC.dialogs.confirm(t('contacts', 'Are you sure you want to delete this contact?'), t('contacts', 'Warning'), function(answer) { + if(answer == true) { + $.getJSON('ajax/deletecard.php',{'id':Contacts.UI.Card.id},function(jsondata){ + if(jsondata.status == 'success'){ + var newid = ''; + var curlistitem = $('#leftcontent [data-id="'+jsondata.data.id+'"]'); + var newlistitem = curlistitem.prev(); + console.log('Previous: ' + newlistitem); + if(newlistitem == undefined) { + newlistitem = curlistitem.next(); } - else{ - OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); + curlistitem.remove(); + if(newlistitem != undefined) { + newid = newlistitem.data('id'); } - }); - } - } - else{ - OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); - //alert(jsondata.data.message); + $('#rightcontent').data('id',newid); + //$('#rightcontent').empty(); + this.id = this.fn = this.fullname = this.shortname = this.famname = this.givname = this.addname = this.honpre = this.honsuf = ''; + this.data = undefined; + // Load first in list. + if($('#contacts li').length > 0) { + Contacts.UI.Card.update(newid); + } else { + // load intro page + $.getJSON('ajax/loadintro.php',{},function(jsondata){ + if(jsondata.status == 'success'){ + id = ''; + $('#rightcontent').data('id',''); + $('#rightcontent').html(jsondata.data.page); + } + else{ + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); + } + }); + } + } + else{ + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); + //alert(jsondata.data.message); + } + }); } }); return false; }, loadContact:function(jsondata){ - $('#contact_communication').hide(); + //$('#contact_communication').hide(); this.data = jsondata; this.id = this.data.id; $('#rightcontent').data('id',this.id); @@ -346,7 +383,6 @@ Contacts={ this.loadPhones(); this.loadAddresses(); this.loadSingleProperties(); - // TODO: load NOTE ;-) if(this.data.NOTE) { $('#note').data('checksum', this.data.NOTE[0]['checksum']); $('#note').find('textarea').val(this.data.NOTE[0]['value']); @@ -354,7 +390,7 @@ Contacts={ } else { $('#note').data('checksum', ''); $('#note').find('textarea').val(''); - $('#note').hide(); + //$('#note').hide(); } }, loadSingleProperties:function() { @@ -468,8 +504,9 @@ Contacts={ return false; }, categoriesChanged:function(newcategories) { // Categories added/deleted. - console.log('categoriesChanged for ' + Contacts.UI.Card.id + ' : ' + newcategories); - categories = newcategories; + categories = $.map(newcategories, function(v) {return v;}); + console.log('categoriesChanged for ' + Contacts.UI.Card.id + ' : ' + categories); + $('#categories').multiple_autocomplete('option', 'source', categories); var categorylist = $('#categories_value').find('input'); $.getJSON(OC.filePath('contacts', 'ajax', 'categories/categoriesfor.php'),{'id':Contacts.UI.Card.id},function(jsondata){ if(jsondata.status == 'success'){ @@ -499,17 +536,18 @@ Contacts={ },*/ editNew:function(){ // add a new contact this.id = ''; this.fn = ''; this.fullname = ''; this.givname = ''; this.famname = ''; this.addname = ''; this.honpre = ''; this.honsuf = ''; - $.getJSON(OC.filePath('contacts', 'ajax', 'newcontact.php'),{},function(jsondata){ + Contacts.UI.Card.add(';;;;', '', '', true); + /*$.getJSON(OC.filePath('contacts', 'ajax', 'newcontact.php'),{},function(jsondata){ if(jsondata.status == 'success'){ id = ''; $('#rightcontent').data('id',''); $('#rightcontent').html(jsondata.data.page); - Contacts.UI.Card.editName(); + //Contacts.UI.Card.editName(); } else { OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); //alert(jsondata.data.message); } - }); + });*/ }, savePropertyInternal:function(name, fields, oldchecksum, checksum){ // TODO: Add functionality for new fields. @@ -605,45 +643,48 @@ Contacts={ },'json'); } }, - addProperty:function(obj){ - var type = $(obj).data('type'); + addProperty:function(type){ + //var type = $(obj).data('type'); console.log('addProperty:' + type); switch (type) { case 'PHOTO': this.loadPhoto(true); $('#file_upload_form').show(); $('#contacts_propertymenu a[data-type="'+type+'"]').parent().hide(); + $('#file_upload_start').trigger('click'); break; case 'NOTE': $('#note').show(); $('#contacts_propertymenu a[data-type="'+type+'"]').parent().hide(); + $('#note').find('textarea').focus(); break; case 'EMAIL': if($('#emaillist>li').length == 1) { $('#emails').show(); } Contacts.UI.Card.addMail(); - Contacts.UI.showHideContactInfo(); + //Contacts.UI.showHideContactInfo(); break; case 'TEL': if($('#phonelist>li').length == 1) { $('#phones').show(); } Contacts.UI.Card.addPhone(); - Contacts.UI.showHideContactInfo(); + //Contacts.UI.showHideContactInfo(); break; case 'ADR': if($('#addressdisplay>dl').length == 1) { $('#addresses').show(); } Contacts.UI.Card.editAddress('new', true); - Contacts.UI.showHideContactInfo(); + //Contacts.UI.showHideContactInfo(); break; case 'NICKNAME': case 'ORG': case 'BDAY': case 'CATEGORIES': $('dl dt[data-element="'+type+'"],dd[data-element="'+type+'"]').show(); + $('dd[data-element="'+type+'"]').find('input').focus(); $('#contacts_propertymenu a[data-type="'+type+'"]').parent().hide(); break; } @@ -657,8 +698,8 @@ Contacts={ if(jsondata.status == 'success'){ if(type == 'list') { Contacts.UI.propertyContainerFor(obj).remove(); - Contacts.UI.showHideContactInfo(); - Contacts.UI.checkListFor(obj); + //Contacts.UI.showHideContactInfo(); + //Contacts.UI.checkListFor(obj); } else if(type == 'single') { var proptype = Contacts.UI.propertyTypeFor(obj); console.log('deleteProperty, hiding: ' + proptype); @@ -667,9 +708,16 @@ Contacts={ console.log('NOTE or PHOTO'); Contacts.UI.propertyContainerFor(obj).hide(); Contacts.UI.propertyContainerFor(obj).data('checksum', ''); + if(proptype == 'PHOTO') { + console.log('Delete PHOTO'); + Contacts.UI.Contacts.refreshThumbnail(Contacts.UI.Card.id); + } else if(proptype == 'NOTE') { + $('#note').find('textarea').val(''); + } } else { $('dl dt[data-element="'+proptype+'"],dd[data-element="'+proptype+'"]').hide(); $('dl dd[data-element="'+proptype+'"]').data('checksum', ''); + $('dl dd[data-element="'+proptype+'"]').find('input').val(''); } $('#contacts_propertymenu a[data-type="'+proptype+'"]').parent().show(); Contacts.UI.loading(obj, false); @@ -686,8 +734,8 @@ Contacts={ } else { // Property hasn't been saved so there's nothing to delete. if(type == 'list') { Contacts.UI.propertyContainerFor(obj).remove(); - Contacts.UI.showHideContactInfo(); - Contacts.UI.checkListFor(obj); + //Contacts.UI.showHideContactInfo(); + //Contacts.UI.checkListFor(obj); } else if(type == 'single') { var proptype = Contacts.UI.propertyTypeFor(obj); console.log('deleteProperty, hiding: ' + proptype); @@ -859,7 +907,7 @@ Contacts={ if(isnew) { container.remove(); } - Contacts.UI.showHideContactInfo(); + //Contacts.UI.showHideContactInfo(); } }, close : function(event, ui) { @@ -868,11 +916,95 @@ Contacts={ if(isnew) { container.remove(); } - Contacts.UI.showHideContactInfo(); - }/*, + //Contacts.UI.showHideContactInfo(); + }, open : function(event, ui) { - // load 'ADR' property - maybe :-P - }*/ + $( "#adr_city" ).autocomplete({ + source: function( request, response ) { + $.ajax({ + url: "http://ws.geonames.org/searchJSON", + dataType: "jsonp", + data: { + featureClass: "P", + style: "full", + maxRows: 12, + lang: lang, + name_startsWith: request.term + }, + success: function( data ) { + response( $.map( data.geonames, function( item ) { + /*for(var key in item) { + console.log(key + ': ' + item[key]); + }*/ + return { + label: item.name + (item.adminName1 ? ", " + item.adminName1 : "") + ", " + item.countryName, + value: item.name, + country: item.countryName + } + })); + } + }); + }, + minLength: 2, + select: function( event, ui ) { + if(ui.item && $('#adr_country').val().trim().length == 0) { + $('#adr_country').val(ui.item.country); + } + /*log( ui.item ? + "Selected: " + ui.item.label : + "Nothing selected, input was " + this.value);*/ + }, + open: function() { + $( this ).removeClass( "ui-corner-all" ).addClass( "ui-corner-top" ); + }, + close: function() { + $( this ).removeClass( "ui-corner-top" ).addClass( "ui-corner-all" ); + } + }); + $( "#adr_country" ).autocomplete({ + source: function( request, response ) { + $.ajax({ + url: "http://ws.geonames.org/searchJSON", + dataType: "jsonp", + data: { + /*featureClass: "A",*/ + featureCode: "PCLI", + /*countryBias: "true",*/ + /*style: "full",*/ + lang: lang, + maxRows: 12, + name_startsWith: request.term + }, + success: function( data ) { + response( $.map( data.geonames, function( item ) { + for(var key in item) { + console.log(key + ': ' + item[key]); + } + return { + label: item.name, + value: item.name + } + })); + } + }); + }, + minLength: 2, + select: function( event, ui ) { + /*if(ui.item) { + $('#adr_country').val(ui.item.country); + } + log( ui.item ? + "Selected: " + ui.item.label : + "Nothing selected, input was " + this.value);*/ + }, + open: function() { + $( this ).removeClass( "ui-corner-all" ).addClass( "ui-corner-top" ); + }, + close: function() { + $( this ).removeClass( "ui-corner-top" ).addClass( "ui-corner-all" ); + } + }); + } }); } else { alert(jsondata.data.message); @@ -941,7 +1073,7 @@ Contacts={ } }, loadPhoto:function(force){ - if(this.data.PHOTO||force==true) { + //if(this.data.PHOTO||force==true) { $.getJSON('ajax/loadphoto.php',{'id':this.id},function(jsondata){ if(jsondata.status == 'success'){ //alert(jsondata.data.page); @@ -954,11 +1086,11 @@ Contacts={ }); $('#file_upload_form').show(); $('#contacts_propertymenu a[data-type="PHOTO"]').parent().hide(); - } else { + /*} else { $('#contacts_details_photo_wrapper').empty(); $('#file_upload_form').hide(); $('#contacts_propertymenu a[data-type="PHOTO"]').parent().show(); - } + }*/ }, editPhoto:function(id, tmp_path){ //alert('editPhoto: ' + tmp_path); @@ -990,7 +1122,7 @@ Contacts={ OC.dialogs.alert(response.data.message, t('contacts', 'Error')); } }); - $('#contacts [data-id="'+this.id+'"]').find('a').css('background','url(thumbnail.php?id='+this.id+'&refresh=1'+Math.random()+') no-repeat'); + Contacts.UI.Contacts.refreshThumbnail(this.id); }, addMail:function() { //alert('addMail'); @@ -1133,7 +1265,7 @@ Contacts={ }); } }, - import:function(){ + doImport:function(){ Contacts.UI.notImplemented(); }, submit:function(button, bookid){ @@ -1166,9 +1298,7 @@ Contacts={ } }, Contacts:{ - /** - * Reload the contacts list. - */ + // Reload the contacts list. update:function(){ console.log('Contacts.update, start'); $.getJSON('ajax/contacts.php',{},function(jsondata){ @@ -1183,15 +1313,16 @@ Contacts={ }); setTimeout(Contacts.UI.Contacts.lazyupdate, 500); }, - /** - * Add thumbnails to the contact list as they become visible in the viewport. - */ + // Add thumbnails to the contact list as they become visible in the viewport. lazyupdate:function(){ $('#contacts li').live('inview', function(){ if (!$(this).find('a').attr('style')) { $(this).find('a').css('background','url(thumbnail.php?id='+$(this).data('id')+') no-repeat'); } }); + }, + refreshThumbnail:function(id){ + $('#contacts [data-id="'+id+'"]').find('a').css('background','url(thumbnail.php?id='+id+'&refresh=1'+Math.random()+') no-repeat'); } } } @@ -1202,9 +1333,6 @@ $(document).ready(function(){ OCCategories.changed = Contacts.UI.Card.categoriesChanged; OCCategories.app = 'contacts'; - /** - * Show the Addressbook chooser - */ $('#chooseaddressbook').click(function(){ Contacts.UI.Addressbooks.overview(); return false; @@ -1219,6 +1347,7 @@ $(document).ready(function(){ */ $('#leftcontent li').live('click',function(){ var id = $(this).data('id'); + $(this).addClass('active'); var oldid = $('#rightcontent').data('id'); if(oldid != 0){ $('#leftcontent li[data-id="'+oldid+'"]').removeClass('active'); @@ -1236,7 +1365,7 @@ $(document).ready(function(){ }); $('#contacts_deletecard').live('click',function(){ - Contacts.UI.Card.delete(); + Contacts.UI.Card.doDelete(); }); $('#contacts li').bind('inview', function(event, isInView, visiblePartX, visiblePartY) { @@ -1394,7 +1523,8 @@ $(document).ready(function(){ } }); $('#contacts_propertymenu a').live('click',function(){ - Contacts.UI.Card.addProperty(this); + var type = $(this).data('type'); + Contacts.UI.Card.addProperty(type); $('#contacts_propertymenu').hide(); }); }); diff --git a/apps/contacts/js/jquery.combobox.js b/apps/contacts/js/jquery.combobox.js index 6da4ecb514..f46d7c14c1 100644 --- a/apps/contacts/js/jquery.combobox.js +++ b/apps/contacts/js/jquery.combobox.js @@ -72,17 +72,10 @@ .appendTo( ul ); }; - this.button = $( "" ) + /*this.button = $( "" ) .attr( "tabIndex", -1 ) .attr( "title", "Show All Items" ) .insertAfter( input ) - /*.button({ - icons: { - primary: "ui-icon-triangle-1-s" - }, - text: false - }) - .removeClass( "ui-corner-all" )*/ .addClass('svg') .addClass('action') .addClass('combo-button') @@ -99,7 +92,7 @@ // pass empty string as value to search for, displaying all results input.autocomplete( "search", "" ); input.focus(); - }); + });*/ $.each(this.options, function(key, value) { self._setOption(key, value); }); @@ -123,17 +116,23 @@ case "id": this.options['id'] = value; this.input.attr('id', value); - break; + break; case "name": this.options['name'] = value; this.input.attr('name', value); - break; + break; + case "attributes": + var input = this.input; + $.each(this.options['attributes'], function(key, value) { + input.attr(key, value); + }); + break; case "classes": var input = this.input; $.each(this.options['classes'], function(key, value) { input.addClass(value); }); - break; + break; } // In jQuery UI 1.8, you have to manually invoke the _setOption method from the base widget $.Widget.prototype._setOption.apply( this, arguments ); diff --git a/apps/contacts/js/jquery.multi-autocomplete.js b/apps/contacts/js/jquery.multi-autocomplete.js index 1c923a2543..e1c5d63dc5 100644 --- a/apps/contacts/js/jquery.multi-autocomplete.js +++ b/apps/contacts/js/jquery.multi-autocomplete.js @@ -5,14 +5,25 @@ (function( $ ) { $.widget('ui.multiple_autocomplete', { _create: function() { + var self = this; function split( val ) { return val.split( /,\s*/ ); } function extractLast( term ) { return split( term ).pop(); } + function showOptions() { + if(!self.element.autocomplete('widget').is(':visible') && self.element.val().trim() == '') { + self.element.autocomplete('search', ''); + } + } //console.log('_create: ' + this.options['id']); - var self = this; + this.element.bind('click', function( event ) { + showOptions(); + }); + this.element.bind('input', function( event ) { + showOptions(); + }); this.element.bind('blur', function( event ) { var tmp = self.element.val().trim(); if(tmp[tmp.length-1] == ',') { @@ -51,7 +62,7 @@ return false; } }); - this.button = $( "" ) + /*this.button = $( "" ) .attr( "tabIndex", -1 ) .attr( "title", "Show All Items" ) .insertAfter( this.element ) @@ -75,7 +86,7 @@ // pass empty string as value to search for, displaying all results self.element.autocomplete( "search", "" ); self.element.focus(); - }); + });*/ }, }); })( jQuery ); diff --git a/apps/contacts/js/loader.js b/apps/contacts/js/loader.js index eb59185d72..95fd7dc94e 100644 --- a/apps/contacts/js/loader.js +++ b/apps/contacts/js/loader.js @@ -77,5 +77,7 @@ $(document).ready(function(){ if(typeof FileActions !== 'undefined'){ FileActions.register('text/vcard','importaddressbook', '', Contacts_Import.importdialog); FileActions.setDefault('text/vcard','importaddressbook'); + FileActions.register('text/x-vcard','importaddressbook', '', Contacts_Import.importdialog); + FileActions.setDefault('text/x-vcard','importaddressbook'); }; }); \ No newline at end of file diff --git a/apps/contacts/lib/addressbook.php b/apps/contacts/lib/addressbook.php index 052c19e55f..9061fa1914 100644 --- a/apps/contacts/lib/addressbook.php +++ b/apps/contacts/lib/addressbook.php @@ -169,7 +169,7 @@ class OC_Contacts_Addressbook{ $uid = OC_User::getUser(); } $prefbooks = OC_Preferences::getValue($uid,'contacts','openaddressbooks',null); - if(is_null($prefbooks)){ + if(!$prefbooks){ $addressbooks = OC_Contacts_Addressbook::all($uid); if(count($addressbooks) == 0){ OC_Contacts_Addressbook::add($uid,'default','Default Address Book'); diff --git a/apps/contacts/lib/app.php b/apps/contacts/lib/app.php index cc33c73300..475d2c8dc2 100644 --- a/apps/contacts/lib/app.php +++ b/apps/contacts/lib/app.php @@ -9,7 +9,7 @@ /** * This class manages our app actions */ -OC_Contacts_App::$l10n = new OC_L10N('contacts'); +OC_Contacts_App::$l10n = OC_L10N::get('contacts'); OC_Contacts_App::$categories = new OC_VCategories('contacts'); class OC_Contacts_App { public static $l10n; @@ -53,11 +53,12 @@ class OC_Contacts_App { if( $addressbook === false || $addressbook['userid'] != OC_User::getUser()) { if ($addressbook === false) { OC_Log::write('contacts', 'Addressbook not found: '. $id, OC_Log::ERROR); + OC_JSON::error(array('data' => array( 'message' => self::$l10n->t('Addressbook not found.')))); } else { OC_Log::write('contacts', 'Addressbook('.$id.') is not from '.OC_User::getUser(), OC_Log::ERROR); + OC_JSON::error(array('data' => array( 'message' => self::$l10n->t('This is not your addressbook.')))); } - OC_JSON::error(array('data' => array( 'message' => self::$l10n->t('This is not your addressbook.')))); // Same here (as with the contact error). Could this error be improved? exit(); } return $addressbook; @@ -156,7 +157,45 @@ class OC_Contacts_App { } public static function getCategories() { - return self::$categories->categories(); + $categories = self::$categories->categories(); + if(count($categories) == 0) { + self::scanCategories(); + $categories = self::$categories->categories(); + } + return $categories; + } + + /** + * scan vcards for categories. + * @param $vccontacts VCards to scan. null to check all vcards for the current user. + */ + public static function scanCategories($vccontacts = null) { + if (is_null($vccontacts)) { + $vcaddressbooks = OC_Contacts_Addressbook::all(OC_User::getUser()); + if(count($vcaddressbooks) > 0) { + $vcaddressbookids = array(); + foreach($vcaddressbooks as $vcaddressbook) { + $vcaddressbookids[] = $vcaddressbook['id']; + } + $vccontacts = OC_Contacts_VCard::all($vcaddressbookids); + } + } + if(is_array($vccontacts) && count($vccontacts) > 0) { + $cards = array(); + foreach($vccontacts as $vccontact) { + $cards[] = $vccontact['carddata']; + } + + self::$categories->rescan($cards); + } + } + + /** + * check VCard for new categories. + * @see OC_VCategories::loadFromVObject + */ + public static function loadCategoriesFromVCard(OC_VObject $contact) { + self::$categories->loadFromVObject($contact, true); } public static function setLastModifiedHeader($contact) { diff --git a/apps/contacts/lib/search.php b/apps/contacts/lib/search.php index cf0a5fe699..31d8542091 100644 --- a/apps/contacts/lib/search.php +++ b/apps/contacts/lib/search.php @@ -1,6 +1,6 @@ getAsString('VERSION'); // Add version if needed if($version && $version < '3.0') { @@ -228,13 +228,13 @@ class OC_Contacts_VCard{ * @param string $uri the uri of the card, default based on the UID * @return insertid on success or null if no card. */ - public static function add($aid, $card, $uri=null){ + public static function add($aid, OC_VObject $card, $uri=null){ if(is_null($card)){ OC_Log::write('contacts','OC_Contacts_VCard::add. No vCard supplied', OC_Log::ERROR); return null; }; - OC_Contacts_App::$categories->loadFromVObject($card); + OC_Contacts_App::loadCategoriesFromVCard($card); self::updateValuesFromAdd($card); @@ -267,7 +267,7 @@ class OC_Contacts_VCard{ */ public static function addFromDAVData($id,$uri,$data){ $card = OC_VObject::parse($data); - return self::add($id, $data, $uri); + return self::add($id, $card, $uri); } /** @@ -306,7 +306,7 @@ class OC_Contacts_VCard{ return false; } - OC_Contacts_App::$categories->loadFromVObject($card); + OC_Contacts_App::loadCategoriesFromVCard($card); $fn = $card->getAsString('FN'); if (empty($fn)) { diff --git a/apps/contacts/templates/index.php b/apps/contacts/templates/index.php index af159ce9c6..b14a35e19e 100644 --- a/apps/contacts/templates/index.php +++ b/apps/contacts/templates/index.php @@ -1,6 +1,7 @@
diff --git a/apps/contacts/templates/part.contact.php b/apps/contacts/templates/part.contact.php index cb1e080a40..c09c20fd4e 100644 --- a/apps/contacts/templates/part.contact.php +++ b/apps/contacts/templates/part.contact.php @@ -14,19 +14,19 @@ $id = isset($_['id']) ? $_['id'] : '';
  • t('Email'); ?>
  • t('Address'); ?>
  • t('Note'); ?>
  • -
  • t('Categories'); ?>
  • +
  • t('Groups'); ?>
  • - + -
    +
    - -
    + +
    -
    +
    @@ -37,92 +37,87 @@ $id = isset($_['id']) ? $_['id'] : '';
    -
    > +
    + +
    + -
    -
    +
    -
    - -
    -
    - -
    + + + + +
    - + - + - - - - + + +
    - -
    +
    - - + +
    +
    +
    + +
    +
    +
    +
    @@ -131,10 +126,11 @@ $(document).ready(function(){ if(''!='') { $.getJSON(OC.filePath('contacts', 'ajax', 'contactdetails.php'),{'id':''},function(jsondata){ if(jsondata.status == 'success'){ + $('#leftcontent li[data-id=""]').addClass('active'); Contacts.UI.Card.loadContact(jsondata.data); } else{ - Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message); + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); } }); } diff --git a/apps/contacts/templates/part.edit_address_dialog.php b/apps/contacts/templates/part.edit_address_dialog.php index 0ecdc4e191..507a3acaa0 100644 --- a/apps/contacts/templates/part.edit_address_dialog.php +++ b/apps/contacts/templates/part.edit_address_dialog.php @@ -22,44 +22,44 @@ foreach(isset($adr['parameters']['TYPE'])?array($adr['parameters']['TYPE']):arra
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    diff --git a/apps/contacts/templates/part.no_contacts.php b/apps/contacts/templates/part.no_contacts.php new file mode 100644 index 0000000000..6c21511e86 --- /dev/null +++ b/apps/contacts/templates/part.no_contacts.php @@ -0,0 +1,8 @@ +
    + t('You have no contacts in your addressbook.') ?> +
    + + + +
    +
    diff --git a/apps/contacts/templates/settings.php b/apps/contacts/templates/settings.php index f56de0ec8b..5627a15c50 100644 --- a/apps/contacts/templates/settings.php +++ b/apps/contacts/templates/settings.php @@ -8,5 +8,6 @@
    t('iOS/OS X'); ?>
    /principals//
    + Powered by geonames.org webservice diff --git a/apps/external/appinfo/info.xml b/apps/external/appinfo/info.xml index 05f5709916..83130f17e6 100644 --- a/apps/external/appinfo/info.xml +++ b/apps/external/appinfo/info.xml @@ -3,7 +3,6 @@ external External Show external Application in the ownCloud menu - 1.0 AGPL Frank Karlitschek 2 diff --git a/apps/external/appinfo/version b/apps/external/appinfo/version new file mode 100644 index 0000000000..9f8e9b69a3 --- /dev/null +++ b/apps/external/appinfo/version @@ -0,0 +1 @@ +1.0 \ No newline at end of file diff --git a/apps/files_archive/appinfo/app.php b/apps/files_archive/appinfo/app.php index 693c28d98a..67809ec980 100644 --- a/apps/files_archive/appinfo/app.php +++ b/apps/files_archive/appinfo/app.php @@ -7,7 +7,8 @@ */ OC::$CLASSPATH['OC_Archive'] = 'apps/files_archive/lib/archive.php'; -foreach(array('ZIP') as $type){ +OC::$CLASSPATH['Archive_Tar'] = '3rdparty/Archive/Tar.php'; +foreach(array('ZIP','TAR') as $type){ OC::$CLASSPATH['OC_Archive_'.$type] = 'apps/files_archive/lib/'.strtolower($type).'.php'; } diff --git a/apps/files_archive/appinfo/info.xml b/apps/files_archive/appinfo/info.xml index df767d39f6..b04498aa08 100644 --- a/apps/files_archive/appinfo/info.xml +++ b/apps/files_archive/appinfo/info.xml @@ -3,8 +3,10 @@ files_archive Archive support Transparent opening of archives - 0.1 AGPL Robin Appelman 3 + + +
    diff --git a/apps/files_archive/appinfo/version b/apps/files_archive/appinfo/version new file mode 100644 index 0000000000..ceab6e11ec --- /dev/null +++ b/apps/files_archive/appinfo/version @@ -0,0 +1 @@ +0.1 \ No newline at end of file diff --git a/apps/files_archive/js/archive.js b/apps/files_archive/js/archive.js index ec316c7bf2..531eb61c01 100644 --- a/apps/files_archive/js/archive.js +++ b/apps/files_archive/js/archive.js @@ -11,5 +11,9 @@ $(document).ready(function() { window.location='index.php?dir='+encodeURIComponent($('#dir').val()).replace(/%2F/g, '/')+'/'+encodeURIComponent(filename); }); FileActions.setDefault('application/zip','Open'); + FileActions.register('application/x-gzip','Open','',function(filename){ + window.location='index.php?dir='+encodeURIComponent($('#dir').val()).replace(/%2F/g, '/')+'/'+encodeURIComponent(filename); + }); + FileActions.setDefault('application/x-gzip','Open'); } }); diff --git a/apps/files_archive/lib/archive.php b/apps/files_archive/lib/archive.php index be89f894fb..113f92e960 100644 --- a/apps/files_archive/lib/archive.php +++ b/apps/files_archive/lib/archive.php @@ -17,6 +17,15 @@ abstract class OC_Archive{ switch($ext){ case '.zip': return new OC_Archive_ZIP($path); + case '.gz': + case '.bz': + case '.bz2': + if(strpos($path,'.tar.')){ + return new OC_Archive_TAR($path); + } + break; + case '.tgz': + return new OC_Archive_TAR($path); } } @@ -77,6 +86,13 @@ abstract class OC_Archive{ * @return bool */ abstract function extractFile($path,$dest); + /** + * extract the archive + * @param string path + * @param string dest + * @return bool + */ + abstract function extract($dest); /** * check if a file or folder exists in the archive * @param string path diff --git a/apps/files_archive/lib/storage.php b/apps/files_archive/lib/storage.php index 72a96ca5a5..700d963304 100644 --- a/apps/files_archive/lib/storage.php +++ b/apps/files_archive/lib/storage.php @@ -125,7 +125,7 @@ class OC_Filestorage_Archive extends OC_Filestorage_Common{ self::$rootView=new OC_FilesystemView(''); } self::$enableAutomount=false;//prevent recursion - $supported=array('zip'); + $supported=array('zip','tar.gz','tar.bz2','tgz'); foreach($supported as $type){ $ext='.'.$type.'/'; if(($pos=strpos(strtolower($path),$ext))!==false){ @@ -139,4 +139,8 @@ class OC_Filestorage_Archive extends OC_Filestorage_Common{ } self::$enableAutomount=true; } + + public function rename($path1,$path2){ + return $this->archive->rename($path1,$path2); + } } diff --git a/apps/files_archive/lib/tar.php b/apps/files_archive/lib/tar.php new file mode 100644 index 0000000000..1eed11a762 --- /dev/null +++ b/apps/files_archive/lib/tar.php @@ -0,0 +1,277 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class OC_Archive_TAR extends OC_Archive{ + const PLAIN=0; + const GZIP=1; + const BZIP=2; + + /** + * @var Archive_Tar tar + */ + private $tar=null; + private $path; + + function __construct($source){ + $types=array(null,'gz','bz'); + $this->path=$source; + $this->tar=new Archive_Tar($source,$types[self::getTarType($source)]); + } + + /** + * try to detect the type of tar compression + * @param string file + * @return str + */ + static public function getTarType($file){ + if(strpos($file,'.')){ + $extension=substr($file,strrpos($file,'.')); + switch($extension){ + case 'gz': + case 'tgz': + return self::GZIP; + case 'bz': + case 'bz2': + return self::BZIP; + default: + return self::PLAIN; + } + }else{ + return self::PLAIN; + } + } + + /** + * add an empty folder to the archive + * @param string path + * @return bool + */ + function addFolder($path){ + $tmpBase=get_temp_dir().'/'; + if(substr($path,-1,1)!='/'){ + $path.='/'; + } + if($this->fileExists($path)){ + return false; + } + mkdir($tmpBase.$path); + $result=$this->tar->addModify(array($tmpBase.$path),'',$tmpBase); + rmdir($tmpBase.$path); + return $result; + } + /** + * add a file to the archive + * @param string path + * @param string source either a local file or string data + * @return bool + */ + function addFile($path,$source=''){ + if($this->fileExists($path)){ + $this->remove($path); + } + if(file_exists($source)){ + $header=array(); + $dummy=''; + $this->tar->_openAppend(); + $result=$this->tar->_addfile($source,$header,$dummy,$dummy,$path); + }else{ + $result=$this->tar->addString($path,$source); + } + return $result; + } + + /** + * rename a file or folder in the archive + * @param string source + * @param string dest + * @return bool + */ + function rename($source,$dest){ + //no proper way to delete, rename entire archive, rename file and remake archive + $tmp=OC_Helper::tmpFolder(); + $this->tar->extract($tmp); + rename($tmp.$source,$tmp.$dest); + $this->tar=null; + unlink($this->path); + $types=array(null,'gz','bz'); + $this->tar=new Archive_Tar($this->path,$types[self::getTarType($this->path)]); + $this->tar->createModify(array($tmp),'',$tmp.'/'); + } + + private function getHeader($file){ + $headers=$this->tar->listContent(); + foreach($headers as $header){ + if($file==$header['filename'] or $file.'/'==$header['filename']){ + return $header; + } + } + return null; + } + + /** + * get the uncompressed size of a file in the archive + * @param string path + * @return int + */ + function filesize($path){ + $stat=$this->getHeader($path); + return $stat['size']; + } + /** + * get the last modified time of a file in the archive + * @param string path + * @return int + */ + function mtime($path){ + $stat=$this->getHeader($path); + return $stat['mtime']; + } + + /** + * get the files in a folder + * @param path + * @return array + */ + function getFolder($path){ + $files=$this->getFiles(); + $folderContent=array(); + $pathLength=strlen($path); + foreach($files as $file){ + if(substr($file,0,$pathLength)==$path and $file!=$path){ + if(strrpos(substr($file,0,-1),'/')<=$pathLength){ + $folderContent[]=substr($file,$pathLength); + } + } + } + return $folderContent; + } + /** + *get all files in the archive + * @return array + */ + function getFiles(){ + $headers=$this->tar->listContent(); + $files=array(); + foreach($headers as $header){ + $files[]=$header['filename']; + } + return $files; + } + /** + * get the content of a file + * @param string path + * @return string + */ + function getFile($path){ + return $this->tar->extractInString($path); + } + /** + * extract a single file from the archive + * @param string path + * @param string dest + * @return bool + */ + function extractFile($path,$dest){ + $tmp=OC_Helper::tmpFolder(); + if(!$this->fileExists($path)){ + return false; + } + $success=$this->tar->extractList(array($path),$tmp); + if($success){ + rename($tmp.$path,$dest); + } + OC_Helper::rmdirr($tmp); + return $success; + } + /** + * extract the archive + * @param string path + * @param string dest + * @return bool + */ + function extract($dest){ + return $this->tar->extract($dest); + } + /** + * check if a file or folder exists in the archive + * @param string path + * @return bool + */ + function fileExists($path){ + return $this->getHeader($path)!==null; + } + + /** + * remove a file or folder from the archive + * @param string path + * @return bool + */ + function remove($path){ + if(!$this->fileExists($path)){ + return false; + } + //no proper way to delete, extract entire archive, delete file and remake archive + $tmp=OC_Helper::tmpFolder(); + $this->tar->extract($tmp); + OC_Helper::rmdirr($tmp.$path); + $this->tar=null; + unlink($this->path); + $this->reopen(); + $this->tar->createModify(array($tmp),'',$tmp); + return true; + } + /** + * get a file handler + * @param string path + * @param string mode + * @return resource + */ + function getStream($path,$mode){ + if(strrpos($path,'.')!==false){ + $ext=substr($path,strrpos($path,'.')); + }else{ + $ext=''; + } + $tmpFile=OC_Helper::tmpFile($ext); + if($this->fileExists($path)){ + $this->extractFile($path,$tmpFile); + }elseif($mode=='r' or $mode=='rb'){ + return false; + } + if($mode=='r' or $mode=='rb'){ + return fopen($tmpFile,$mode); + }else{ + OC_CloseStreamWrapper::$callBacks[$tmpFile]=array($this,'writeBack'); + self::$tempFiles[$tmpFile]=$path; + return fopen('close://'.$tmpFile,$mode); + } + } + + private static $tempFiles=array(); + /** + * write back temporary files + */ + function writeBack($tmpFile){ + if(isset(self::$tempFiles[$tmpFile])){ + $this->addFile(self::$tempFiles[$tmpFile],$tmpFile); + unlink($tmpFile); + } + } + + /** + * reopen the archive to ensure everything is written + */ + private function reopen(){ + if($this->tar){ + $this->tar->_close(); + $this->tar=null; + } + $types=array(null,'gz','bz'); + $this->tar=new Archive_Tar($this->path,$types[self::getTarType($this->path)]); + } +} diff --git a/apps/files_archive/lib/zip.php b/apps/files_archive/lib/zip.php index eab101b3a5..5a5bc76687 100644 --- a/apps/files_archive/lib/zip.php +++ b/apps/files_archive/lib/zip.php @@ -11,7 +11,6 @@ class OC_Archive_ZIP extends OC_Archive{ * @var ZipArchive zip */ private $zip=null; - private $contents=array(); private $success=false; private $path; @@ -56,7 +55,9 @@ class OC_Archive_ZIP extends OC_Archive{ * @return bool */ function rename($source,$dest){ - return $this->zip->renameName($source,$dest); + $source=$this->stripPath($source); + $dest=$this->stripPath($dest); + $this->zip->renameName($source,$dest); } /** * get the uncompressed size of a file in the archive @@ -99,15 +100,11 @@ class OC_Archive_ZIP extends OC_Archive{ * @return array */ function getFiles(){ - if(count($this->contents)){ - return $this->contents; - } $fileCount=$this->zip->numFiles; $files=array(); for($i=0;$i<$fileCount;$i++){ $files[]=$this->zip->getNameIndex($i); } - $this->contents=$files; return $files; } /** @@ -128,13 +125,22 @@ class OC_Archive_ZIP extends OC_Archive{ $fp = $this->zip->getStream($path); file_put_contents($dest,$fp); } + /** + * extract the archive + * @param string path + * @param string dest + * @return bool + */ + function extract($dest){ + return $this->zip->extractTo($dest); + } /** * check if a file or folder exists in the archive * @param string path * @return bool */ function fileExists($path){ - return $this->zip->locateName($path)!==false; + return ($this->zip->locateName($path)!==false) or ($this->zip->locateName($path.'/')!==false); } /** * remove a file or folder from the archive @@ -142,7 +148,11 @@ class OC_Archive_ZIP extends OC_Archive{ * @return bool */ function remove($path){ - return $this->zip->deleteName($path); + if($this->fileExists($path.'/')){ + return $this->zip->deleteName($path.'/'); + }else{ + return $this->zip->deleteName($path); + } } /** * get a file handler @@ -179,4 +189,12 @@ class OC_Archive_ZIP extends OC_Archive{ unlink($tmpFile); } } + + private function stripPath($path){ + if(substr($path,0,1)=='/'){ + return substr($path,1); + }else{ + return $path; + } + } } diff --git a/apps/files_archive/tests/archive.php b/apps/files_archive/tests/archive.php index 2e26b5e03b..9e99466a52 100644 --- a/apps/files_archive/tests/archive.php +++ b/apps/files_archive/tests/archive.php @@ -27,10 +27,10 @@ abstract class Test_Archive extends UnitTestCase { $this->instance=$this->getExisting(); $allFiles=$this->instance->getFiles(); $expected=array('lorem.txt','logo-wide.png','dir/','dir/lorem.txt'); - $this->assertEqual(4,count($allFiles)); + $this->assertEqual(4,count($allFiles),'only found '.count($allFiles).' out of 4 expected files'); foreach($expected as $file){ $this->assertNotIdentical(false,array_search($file,$allFiles),'cant find '.$file.' in archive'); - $this->assertTrue($this->instance->fileExists($file)); + $this->assertTrue($this->instance->fileExists($file),'file '.$file.' does not exist in archive'); } $this->assertFalse($this->instance->fileExists('non/existing/file')); @@ -68,6 +68,7 @@ abstract class Test_Archive extends UnitTestCase { $this->instance->addFile('lorem.txt',$textFile); $this->assertEqual(1,count($this->instance->getFiles())); $this->assertTrue($this->instance->fileExists('lorem.txt')); + $this->assertFalse($this->instance->fileExists('lorem.txt/')); $this->assertEqual(file_get_contents($textFile),$this->instance->getFile('lorem.txt')); $this->instance->addFile('lorem.txt','foobar'); @@ -94,4 +95,39 @@ abstract class Test_Archive extends UnitTestCase { $this->assertTrue($this->instance->fileExists('lorem.txt')); $this->assertEqual(file_get_contents($dir.'/lorem.txt'),$this->instance->getFile('lorem.txt')); } + public function testFolder(){ + $this->instance=$this->getNew(); + $this->assertFalse($this->instance->fileExists('/test')); + $this->assertFalse($this->instance->fileExists('/test/')); + $this->instance->addFolder('/test'); + $this->assertTrue($this->instance->fileExists('/test')); + $this->assertTrue($this->instance->fileExists('/test/')); + $this->instance->remove('/test'); + $this->assertFalse($this->instance->fileExists('/test')); + $this->assertFalse($this->instance->fileExists('/test/')); + } + public function testExtract(){ + $dir=OC::$SERVERROOT.'/apps/files_archive/tests/data'; + $this->instance=$this->getExisting(); + $tmpDir=OC_Helper::tmpFolder(); + $this->instance->extract($tmpDir); + $this->assertEqual(true,file_exists($tmpDir.'lorem.txt')); + $this->assertEqual(true,file_exists($tmpDir.'dir/lorem.txt')); + $this->assertEqual(true,file_exists($tmpDir.'logo-wide.png')); + $this->assertEqual(file_get_contents($dir.'/lorem.txt'),file_get_contents($tmpDir.'lorem.txt')); + OC_Helper::rmdirr($tmpDir); + } + public function testMoveRemove(){ + $dir=OC::$SERVERROOT.'/apps/files_archive/tests/data'; + $textFile=$dir.'/lorem.txt'; + $this->instance=$this->getNew(); + $this->instance->addFile('lorem.txt',$textFile); + $this->assertFalse($this->instance->fileExists('target.txt')); + $this->instance->rename('lorem.txt','target.txt'); + $this->assertTrue($this->instance->fileExists('target.txt')); + $this->assertFalse($this->instance->fileExists('lorem.txt')); + $this->assertEqual(file_get_contents($textFile),$this->instance->getFile('target.txt')); + $this->instance->remove('target.txt'); + $this->assertFalse($this->instance->fileExists('target.txt')); + } } diff --git a/apps/files_archive/tests/tar.php b/apps/files_archive/tests/tar.php new file mode 100644 index 0000000000..193a65b550 --- /dev/null +++ b/apps/files_archive/tests/tar.php @@ -0,0 +1,20 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +require_once('archive.php'); + +class Test_Archive_TAR extends Test_Archive{ + protected function getExisting(){ + $dir=OC::$SERVERROOT.'/apps/files_archive/tests/data'; + return new OC_Archive_TAR($dir.'/data.tar.gz'); + } + + protected function getNew(){ + return new OC_Archive_TAR(OC_Helper::tmpFile('.tar.gz')); + } +} diff --git a/apps/files_encryption/appinfo/info.xml b/apps/files_encryption/appinfo/info.xml index 053044aaed..c2e1aa9604 100644 --- a/apps/files_encryption/appinfo/info.xml +++ b/apps/files_encryption/appinfo/info.xml @@ -3,8 +3,10 @@ files_encryption Encryption Server side encryption of files - 0.1 AGPL Robin Appelman 3 + + + diff --git a/apps/files_encryption/appinfo/version b/apps/files_encryption/appinfo/version new file mode 100644 index 0000000000..ceab6e11ec --- /dev/null +++ b/apps/files_encryption/appinfo/version @@ -0,0 +1 @@ +0.1 \ No newline at end of file diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 0a593b98c4..246d4f672d 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -26,7 +26,7 @@ // - Crypt/decrypt button in the userinterface // - Setting if crypto should be on by default // - Add a setting "Don´t encrypt files larger than xx because of performance reasons" -// - Transparent decrypt/encrpt in filesystem.php. Autodetect if a file is encrypted (.encrypted extensio) +// - Transparent decrypt/encrypt in filesystem.php. Autodetect if a file is encrypted (.encrypted extension) // - Don't use a password directly as encryption key. but a key which is stored on the server and encrypted with the user password. -> password change faster // - IMPORTANT! Check if the block lenght of the encrypted data stays the same diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index c1c26d7754..c68df06f9f 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -41,8 +41,8 @@ class OC_FileProxy_Encryption extends OC_FileProxy{ if(self::isEncrypted($path)){ return true; } - $extention=substr($path,strrpos($path,'.')+1); - if(array_search($extention,self::$blackList)===false){ + $extension=substr($path,strrpos($path,'.')+1); + if(array_search($extension,self::$blackList)===false){ return true; } } diff --git a/apps/files_external/appinfo/app.php b/apps/files_external/appinfo/app.php new file mode 100644 index 0000000000..95770b44b7 --- /dev/null +++ b/apps/files_external/appinfo/app.php @@ -0,0 +1,11 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +OC::$CLASSPATH['OC_Filestorage_FTP']='apps/files_external/lib/ftp.php'; +OC::$CLASSPATH['OC_Filestorage_DAV']='apps/files_external/lib/webdav.php'; +OC::$CLASSPATH['OC_Filestorage_Google']='apps/files_external/lib/google.php'; diff --git a/apps/files_external/appinfo/info.xml b/apps/files_external/appinfo/info.xml new file mode 100644 index 0000000000..1918925389 --- /dev/null +++ b/apps/files_external/appinfo/info.xml @@ -0,0 +1,12 @@ + + + files_external + External storage support + Mount external storage sources + AGPL + Robin Appelman + 3 + + + + diff --git a/apps/files_external/appinfo/version b/apps/files_external/appinfo/version new file mode 100644 index 0000000000..ceab6e11ec --- /dev/null +++ b/apps/files_external/appinfo/version @@ -0,0 +1 @@ +0.1 \ No newline at end of file diff --git a/apps/files_external/lib/ftp.php b/apps/files_external/lib/ftp.php new file mode 100644 index 0000000000..802446b4fd --- /dev/null +++ b/apps/files_external/lib/ftp.php @@ -0,0 +1,157 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class OC_FileStorage_FTP extends OC_Filestorage_Common{ + private $password; + private $user; + private $host; + private $secure; + private $root; + + private static $tempFiles=array(); + + public function __construct($params){ + $this->host=$params['host']; + $this->user=$params['user']; + $this->password=$params['password']; + $this->secure=isset($params['secure'])?(bool)$params['secure']:false; + $this->root=isset($params['root'])?$params['root']:'/'; + if(substr($this->root,0,1)!='/'){ + $this->root='/'.$this->root; + } + + //create the root folder if necesary + mkdir($this->constructUrl('')); + } + + /** + * construct the ftp url + * @param string path + * @return string + */ + public function constructUrl($path){ + $url='ftp'; + if($this->secure){ + $url.='s'; + } + $url.='://'.$this->user.':'.$this->password.'@'.$this->host.$this->root.$path; + return $url; + } + + public function mkdir($path){ + return mkdir($this->constructUrl($path)); + } + + public function rmdir($path){ + if($this->file_exists($path)){ + $succes=rmdir($this->constructUrl($path)); + clearstatcache(); + return $succes; + }else{ + return false; + } + } + + public function opendir($path){ + return opendir($this->constructUrl($path)); + } + + public function filetype($path){ + return filetype($this->constructUrl($path)); + } + + public function is_readable($path){ + return true;//not properly supported + } + + public function is_writable($path){ + return true;//not properly supported + } + + public function file_exists($path){ + return file_exists($this->constructUrl($path)); + } + + public function unlink($path){ + $succes=unlink($this->constructUrl($path)); + clearstatcache(); + return $succes; + } + + public function fopen($path,$mode){ + switch($mode){ + case 'r': + case 'rb': + case 'w': + case 'wb': + case 'a': + case 'ab': + //these are supported by the wrapper + $context = stream_context_create(array('ftp' => array('overwrite' => true))); + return fopen($this->constructUrl($path),$mode,false,$context); + case 'r+': + case 'w+': + case 'wb+': + case 'a+': + case 'x': + case 'x+': + case 'c': + case 'c+': + //emulate these + if(strrpos($path,'.')!==false){ + $ext=substr($path,strrpos($path,'.')); + }else{ + $ext=''; + } + $tmpFile=OC_Helper::tmpFile($ext); + OC_CloseStreamWrapper::$callBacks[$tmpFile]=array($this,'writeBack'); + if($this->file_exists($path)){ + $this->getFile($path,$tmpFile); + } + self::$tempFiles[$tmpFile]=$path; + return fopen('close://'.$tmpFile,$mode); + } + } + + public function writeBack($tmpFile){ + if(isset(self::$tempFiles[$tmpFile])){ + $this->uploadFile($tmpFile,self::$tempFiles[$tmpFile]); + unlink($tmpFile); + } + } + + public function free_space($path){ + return 0; + } + + public function touch($path,$mtime=null){ + if(is_null($mtime)){ + $fh=$this->fopen($path,'a'); + fwrite($fh,''); + fclose($fh); + }else{ + return false;//not supported + } + } + + public function getFile($path,$target){ + return copy($this->constructUrl($path),$target); + } + + public function uploadFile($path,$target){ + return copy($path,$this->constructUrl($target)); + } + + public function rename($path1,$path2){ + return rename($this->constructUrl($path1),$this->constructUrl($path2)); + } + + public function stat($path){ + return stat($this->constructUrl($path)); + } +} diff --git a/lib/filestorage/google.php b/apps/files_external/lib/google.php similarity index 73% rename from lib/filestorage/google.php rename to apps/files_external/lib/google.php index 4998554838..0d6db1987f 100644 --- a/lib/filestorage/google.php +++ b/apps/files_external/lib/google.php @@ -24,14 +24,12 @@ require_once 'common.inc.php'; class OC_Filestorage_Google extends OC_Filestorage_Common { - private $datadir; private $consumer; private $oauth_token; private $sig_method; private $entries; public function __construct($arguments) { - $this->datadir = $arguments['datadir']; $consumer_key = isset($arguments['consumer_key']) ? $arguments['consumer_key'] : 'anonymous'; $consumer_secret = isset($arguments['consumer_secret']) ? $arguments['consumer_secret'] : 'anonymous'; $this->consumer = new OAuthConsumer($consumer_key, $consumer_secret); @@ -40,7 +38,7 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { $this->entries = array(); } - private function sendRequest($feedUri, $http_method, $postData = null) { + private function sendRequest($feedUri, $http_method, $isDownload = false, $postData = null) { $feedUri = trim($feedUri); // create an associative array from each key/value url query param pair. $params = array(); @@ -54,30 +52,71 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { $tempStr .= '&' . urlencode($key) . '=' . urlencode($value); } $feedUri = preg_replace('/&/', '?', $tempStr, 1); - $req = OAuthRequest::from_consumer_and_token($this->consumer, $this->oauth_token, $http_method, $feedUri, $params); - $req->sign_request($this->sig_method, $this->consumer, $this->oauth_token); - $auth_header = $req->to_header(); - $result = send_signed_request($http_method, $feedUri, array($auth_header, 'Content-Type: application/atom+xml', 'GData-Version: 3.0'), $postData); - // TODO Return false if error is received - if (!$result) { - return false; + $request = OAuthRequest::from_consumer_and_token($this->consumer, $this->oauth_token, $http_method, $feedUri, $params); + $request->sign_request($this->sig_method, $this->consumer, $this->oauth_token); + $auth_header = $request->to_header(); + $headers = array($auth_header, 'Content-Type: application/atom+xml', 'GData-Version: 3.0'); + $curl = curl_init($feedUri); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_FAILONERROR, false); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + switch ($http_method) { + case 'GET': + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); + break; + case 'POST': + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $postData); + break; + case 'PUT': + $headers[] = 'If-Match: *'; + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $http_method); + curl_setopt($curl, CURLOPT_POSTFIELDS, $postData); + break; + case 'DELETE': + $headers[] = 'If-Match: *'; + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $http_method); + break; + default: + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); } - $result = explode('<', $result, 2); - $result = isset($result[1]) ? '<'.$result[1] : $result[0]; + if ($isDownload) { + $tmpFile = OC_Helper::tmpFile(); + $fp = fopen($tmpFile, 'w'); + curl_setopt($curl, CURLOPT_FILE, $fp); + curl_exec($curl); + curl_close($curl); + return $tmpFile; + } + $result = curl_exec($curl); + curl_close($curl); $dom = new DOMDocument(); $dom->loadXML($result); return $dom; } private function getResource($path) { - if (array_key_exists($path, $this->entries)) { - return $this->entries[$path]; + $file = basename($path); + if (array_key_exists($file, $this->entries)) { + return $this->entries[$file]; } else { - $title = basename($path); - $dom = $this->sendRequest('https://docs.google.com/feeds/default/private/full?showfolders=true&title='.$title, 'GET'); + // Strip the file extension; file could be a native Google Docs resource + if ($pos = strpos($file, '.')) { + $title = substr($file, 0, $pos); + $dom = $this->sendRequest('https://docs.google.com/feeds/default/private/full?showfolders=true&title='.$title, 'GET'); + // Check if request was successful and entry exists + if ($dom && $entry = $dom->getElementsByTagName('entry')->item(0)) { + $this->entries[$file] = $entry; + return $entry; + } + } + $dom = $this->sendRequest('https://docs.google.com/feeds/default/private/full?showfolders=true&title='.$file, 'GET'); // Check if request was successful and entry exists if ($dom && $entry = $dom->getElementsByTagName('entry')->item(0)) { - $this->entries[$path] = $entry; + $this->entries[$file] = $entry; return $entry; } return false; @@ -86,7 +125,7 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { private function getExtension($entry) { $mimetype = $this->getMimeType('', $entry); - switch($mimetype) { + switch ($mimetype) { case 'httpd/unix-directory': return ''; case 'application/vnd.oasis.opendocument.text': @@ -158,7 +197,10 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { $name = $entry->getElementsByTagName('title')->item(0)->nodeValue; // Google Docs resources don't always include extensions in title if (!strpos($name, '.')) { - $name .= '.'.$this->getExtension($entry); + $extension = $this->getExtension($entry); + if ($extension != '') { + $name .= '.'.$extension; + } } $files[] = $name; // Cache entry for future use @@ -178,11 +220,15 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { } else if ($entry = $this->getResource($path)) { // NOTE: Native resources don't have a file size $stat['size'] = $entry->getElementsByTagNameNS('http://schemas.google.com/g/2005', 'quotaBytesUsed')->item(0)->nodeValue; - $stat['atime'] = strtotime($entry->getElementsByTagNameNS('http://schemas.google.com/g/2005', 'lastViewed')->item(0)->nodeValue); +// if (isset($atime = $entry->getElementsByTagNameNS('http://schemas.google.com/g/2005', 'lastViewed')->item(0)->nodeValue)) +// $stat['atime'] = strtotime($entry->getElementsByTagNameNS('http://schemas.google.com/g/2005', 'lastViewed')->item(0)->nodeValue); $stat['mtime'] = strtotime($entry->getElementsByTagName('updated')->item(0)->nodeValue); $stat['ctime'] = strtotime($entry->getElementsByTagName('published')->item(0)->nodeValue); } - return $stat; + if (isset($stat)) { + return $stat; + } + return false; } public function filetype($path) { @@ -278,14 +324,36 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { public function fopen($path, $mode) { if ($entry = $this->getResource($path)) { - $extension = $this->getExtension($path); - $downloadUri = $entry->getElementsByTagName('content')->item(0)->getAttribute('src'); - // TODO Non-native documents don't need these additional parameters - $downloadUri .= '&exportFormat='.$extension.'&format='.$extension; + switch ($mode) { + case 'r': + case 'rb': + $extension = $this->getExtension($entry); + $downloadUri = $entry->getElementsByTagName('content')->item(0)->getAttribute('src'); + // TODO Non-native documents don't need these additional parameters + $downloadUri .= '&exportFormat='.$extension.'&format='.$extension; + $tmpFile = $this->sendRequest($downloadUri, 'GET', true); + return fopen($tmpFile, 'r'); + case 'w': + case 'wb': + case 'a': + case 'ab': + case 'r+': + case 'w+': + case 'wb+': + case 'a+': + case 'x': + case 'x+': + case 'c': + case 'c+': + // TODO Edit documents + } + } + return false; } public function getMimeType($path, $entry = null) { + // Entry can be passed, because extension is required for opendir and the entry can't be cached without the extension if ($entry == null) { if ($path == '' || $path == '/') { return 'httpd/unix-directory'; @@ -297,7 +365,7 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { $mimetype = $entry->getElementsByTagName('content')->item(0)->getAttribute('type'); // Native Google Docs resources often default to text/html, but it may be more useful to default to a corresponding ODF mimetype // Collections get reported as application/atom+xml, make sure it actually is a folder and fix the mimetype - if ($mimetype == 'text/html' || $mimetype == 'application/atom+xml') { + if ($mimetype == 'text/html' || $mimetype == 'application/atom+xml;type=feed') { $categories = $entry->getElementsByTagName('category'); foreach ($categories as $category) { if ($category->getAttribute('scheme') == 'http://schemas.google.com/g/2005#kind') { @@ -334,8 +402,8 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { return false; } - public function search($query) { - + public function touch($path, $mtime = null) { + } } \ No newline at end of file diff --git a/apps/files_external/lib/webdav.php b/apps/files_external/lib/webdav.php new file mode 100644 index 0000000000..7a2da5c8ec --- /dev/null +++ b/apps/files_external/lib/webdav.php @@ -0,0 +1,293 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class OC_FileStorage_DAV extends OC_Filestorage_Common{ + private $password; + private $user; + private $host; + private $secure; + private $root; + /** + * @var Sabre_DAV_Client + */ + private $client; + + private static $tempFiles=array(); + + public function __construct($params){ + $this->host=$params['host']; + $this->user=$params['user']; + $this->password=$params['password']; + $this->secure=isset($params['secure'])?(bool)$params['secure']:false; + $this->root=isset($params['root'])?$params['root']:'/'; + if(substr($this->root,0,1)!='/'){ + $this->root='/'.$this->root; + } + if(substr($this->root,-1,1)!='/'){ + $this->root.='/'; + } + + $settings = array( + 'baseUri' => $this->createBaseUri(), + 'userName' => $this->user, + 'password' => $this->password, + ); + $this->client = new Sabre_DAV_Client($settings); + + //create the root folder if necesary + $this->mkdir(''); + } + + private function createBaseUri(){ + $baseUri='http'; + if($this->secure){ + $baseUri.'s'; + } + $baseUri.='://'.$this->host.$this->root; + return $baseUri; + } + + public function mkdir($path){ + $path=$this->cleanPath($path); + return $this->simpleResponse('MKCOL',$path,null,201); + } + + public function rmdir($path){ + $path=$this->cleanPath($path); + return $this->simpleResponse('DELETE',$path,null,204); + } + + public function opendir($path){ + $path=$this->cleanPath($path); + try{ + $response=$this->client->propfind($path, array(),1); + $stripLength=strlen($this->root)+strlen($path); + $id=md5('webdav'.$this->root.$path); + OC_FakeDirStream::$dirs[$id]=array(); + foreach($response as $file=>$data){ + //strip root and path + $file=trim(substr($file,$stripLength)); + $file=trim($file,'/'); + if($file){ + OC_FakeDirStream::$dirs[$id][]=$file; + } + } + return opendir('fakedir://'.$id); + }catch(Exception $e){ + return false; + } + } + + public function filetype($path){ + $path=$this->cleanPath($path); + try{ + $response=$this->client->propfind($path, array('{DAV:}resourcetype')); + $responseType=$response["{DAV:}resourcetype"]->resourceType; + return (count($responseType)>0 and $responseType[0]=="{DAV:}collection")?'dir':'file'; + }catch(Exception $e){ + return false; + } + } + + public function is_readable($path){ + return true;//not properly supported + } + + public function is_writable($path){ + return true;//not properly supported + } + + public function file_exists($path){ + $path=$this->cleanPath($path); + try{ + $response=$this->client->propfind($path, array('{DAV:}resourcetype')); + return true;//no 404 exception + }catch(Exception $e){ + return false; + } + } + + public function unlink($path){ + return $this->simpleResponse('DELETE',$path,null,204); + } + + public function fopen($path,$mode){ + $path=$this->cleanPath($path); + switch($mode){ + case 'r': + case 'rb': + //straight up curl instead of sabredav here, sabredav put's the entire get result in memory + $curl = curl_init(); + $fp = fopen('php://temp', 'r+'); + curl_setopt($curl,CURLOPT_USERPWD,$this->user.':'.$this->password); + curl_setopt($curl, CURLOPT_URL, $this->createBaseUri().$path); + curl_setopt($curl, CURLOPT_FILE, $fp); + + curl_exec ($curl); + curl_close ($curl); + rewind($fp); + return $fp; + case 'w': + case 'wb': + case 'a': + case 'ab': + case 'r+': + case 'w+': + case 'wb+': + case 'a+': + case 'x': + case 'x+': + case 'c': + case 'c+': + //emulate these + if(strrpos($path,'.')!==false){ + $ext=substr($path,strrpos($path,'.')); + }else{ + $ext=''; + } + $tmpFile=OC_Helper::tmpFile($ext); + OC_CloseStreamWrapper::$callBacks[$tmpFile]=array($this,'writeBack'); + if($this->file_exists($path)){ + $this->getFile($path,$tmpFile); + } + self::$tempFiles[$tmpFile]=$path; + return fopen('close://'.$tmpFile,$mode); + } + } + + public function writeBack($tmpFile){ + if(isset(self::$tempFiles[$tmpFile])){ + $this->uploadFile($tmpFile,self::$tempFiles[$tmpFile]); + unlink($tmpFile); + } + } + + public function free_space($path){ + $path=$this->cleanPath($path); + try{ + $response=$this->client->propfind($path, array('{DAV:}quota-available-bytes')); + if(isset($response['{DAV:}quota-available-bytes'])){ + return (int)$response['{DAV:}quota-available-bytes']; + }else{ + return 0; + } + }catch(Exception $e){ + return 0; + } + } + + public function touch($path,$mtime=null){ + if(is_null($mtime)){ + $mtime=time(); + } + $path=$this->cleanPath($path); + $this->client->proppatch($path, array('{DAV:}lastmodified' => $mtime,)); + } + + public function getFile($path,$target){ + $source=$this->fopen($path,'r'); + file_put_contents($target,$source); + } + + public function uploadFile($path,$target){ + $source=fopen($path,'r'); + + $curl = curl_init(); + curl_setopt($curl,CURLOPT_USERPWD,$this->user.':'.$this->password); + curl_setopt($curl, CURLOPT_URL, $this->createBaseUri().$target); + curl_setopt($curl, CURLOPT_BINARYTRANSFER, true); + curl_setopt($curl, CURLOPT_INFILE, $source); // file pointer + curl_setopt($curl, CURLOPT_INFILESIZE, filesize($path)); + curl_setopt($curl, CURLOPT_PUT, true); + curl_exec ($curl); + curl_close ($curl); + } + + public function rename($path1,$path2){ + $path1=$this->cleanPath($path1); + $path2=$this->root.$this->cleanPath($path2); + try{ + $response=$this->client->request('MOVE',$path1,null,array('Destination'=>$path2)); + return true; + }catch(Exception $e){ + echo $e; + echo 'fail'; + var_dump($response); + return false; + } + } + + public function copy($path1,$path2){ + $path1=$this->cleanPath($path1); + $path2=$this->root.$this->cleanPath($path2); + try{ + $response=$this->client->request('COPY',$path1,null,array('Destination'=>$path2)); + return true; + }catch(Exception $e){ + echo $e; + echo 'fail'; + var_dump($response); + return false; + } + } + + public function stat($path){ + $path=$this->cleanPath($path); + try{ + $response=$this->client->propfind($path, array('{DAV:}getlastmodified','{DAV:}getcontentlength')); + if(isset($response['{DAV:}getlastmodified']) and isset($response['{DAV:}getcontentlength'])){ + return array( + 'mtime'=>strtotime($response['{DAV:}getlastmodified']), + 'size'=>(int)$response['{DAV:}getcontentlength'], + 'ctime'=>-1, + ); + }else{ + return array(); + } + }catch(Exception $e){ + return array(); + } + } + + public function getMimeType($path){ + $path=$this->cleanPath($path); + try{ + $response=$this->client->propfind($path, array('{DAV:}getcontenttype','{DAV:}resourcetype')); + $responseType=$response["{DAV:}resourcetype"]->resourceType; + $type=(count($responseType)>0 and $responseType[0]=="{DAV:}collection")?'dir':'file'; + if($type=='dir'){ + return 'httpd/unix-directory'; + }elseif(isset($response['{DAV:}getcontenttype'])){ + return $response['{DAV:}getcontenttype']; + }else{ + return false; + } + }catch(Exception $e){ + return false; + } + } + + private function cleanPath($path){ + if(substr($path,0,1)=='/'){ + return substr($path,1); + }else{ + return $path; + } + } + + private function simpleResponse($method,$path,$body,$expected){ + $path=$this->cleanPath($path); + try{ + $response=$this->client->request($method,$path,$body); + return $response['statusCode']==$expected; + }catch(Exception $e){ + return false; + } + } +} + diff --git a/apps/files_external/tests/config.php b/apps/files_external/tests/config.php new file mode 100644 index 0000000000..fa4c74a6e2 --- /dev/null +++ b/apps/files_external/tests/config.php @@ -0,0 +1,25 @@ +array( + 'run'=>false, + 'host'=>'localhost', + 'user'=>'test', + 'password'=>'test', + 'root'=>'/test', + ), + 'webdav'=>array( + 'run'=>false, + 'host'=>'localhost', + 'user'=>'test', + 'password'=>'test', + 'root'=>'/owncloud/files/webdav.php', + ), + 'google'=>array( + 'run'=>false, + 'consumer_key'=>'anonymous', + 'consumer_secret'=>'anonymous', + 'token'=>'test', + 'token_secret'=>'test', + 'root'=>'/google', + ) +); diff --git a/apps/files_external/tests/ftp.php b/apps/files_external/tests/ftp.php new file mode 100644 index 0000000000..e30fb9a1c3 --- /dev/null +++ b/apps/files_external/tests/ftp.php @@ -0,0 +1,30 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +$config=include('apps/files_external/tests/config.php'); +if(!is_array($config) or !isset($config['ftp']) or !$config['ftp']['run']){ + abstract class Test_Filestorage_FTP extends Test_FileStorage{} + return; +}else{ + class Test_Filestorage_FTP extends Test_FileStorage { + private $config; + private $id; + + public function setUp(){ + $id=uniqid(); + $this->config=include('apps/files_external/tests/config.php'); + $this->config['ftp']['root'].='/'.$id;//make sure we have an new empty folder to work in + $this->instance=new OC_Filestorage_FTP($this->config['ftp']); + } + + public function tearDown(){ + OC_Helper::rmdirr($this->instance->constructUrl('')); + } + } +} + diff --git a/apps/files_external/tests/google.php b/apps/files_external/tests/google.php new file mode 100644 index 0000000000..08116f0e74 --- /dev/null +++ b/apps/files_external/tests/google.php @@ -0,0 +1,45 @@ +. +*/ + +$config=include('apps/files_external/tests/config.php'); +if(!is_array($config) or !isset($config['google']) or !$config['google']['run']){ + abstract class Test_Filestorage_Google extends Test_FileStorage{} + return; +}else{ + class Test_Filestorage_Google extends Test_FileStorage { + + private $config; + private $id; + + public function setUp(){ + $id=uniqid(); + $this->config=include('apps/files_external/tests/config.php'); + $this->config['google']['root'].='/'.$id;//make sure we have an new empty folder to work in + $this->instance=new OC_Filestorage_Google($this->config['google']); + } + + public function tearDown(){ + $this->instance->rmdir('/'); + } + } +} + diff --git a/apps/files_external/tests/webdav.php b/apps/files_external/tests/webdav.php new file mode 100644 index 0000000000..144659819b --- /dev/null +++ b/apps/files_external/tests/webdav.php @@ -0,0 +1,30 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +$config=include('apps/files_external/tests/config.php'); +if(!is_array($config) or !isset($config['webdav']) or !$config['webdav']['run']){ + abstract class Test_Filestorage_DAV extends Test_FileStorage{} + return; +}else{ + class Test_Filestorage_DAV extends Test_FileStorage { + private $config; + private $id; + + public function setUp(){ + $id=uniqid(); + $this->config=include('apps/files_external/tests/config.php'); + $this->config['webdav']['root'].='/'.$id;//make sure we have an new empty folder to work in + $this->instance=new OC_Filestorage_DAV($this->config['webdav']); + } + + public function tearDown(){ + $this->instance->rmdir('/'); + } + } +} + diff --git a/apps/files_imageviewer/appinfo/info.xml b/apps/files_imageviewer/appinfo/info.xml index 00b55c254d..dbc78ffba0 100644 --- a/apps/files_imageviewer/appinfo/info.xml +++ b/apps/files_imageviewer/appinfo/info.xml @@ -3,7 +3,6 @@ files_imageviewer Image Viewer Simple image viewer for owncloud - 1.0 AGPL Robin Appelman 2 diff --git a/apps/files_imageviewer/appinfo/version b/apps/files_imageviewer/appinfo/version new file mode 100644 index 0000000000..9f8e9b69a3 --- /dev/null +++ b/apps/files_imageviewer/appinfo/version @@ -0,0 +1 @@ +1.0 \ No newline at end of file diff --git a/apps/files_pdfviewer/appinfo/info.xml b/apps/files_pdfviewer/appinfo/info.xml index f133f1900d..0e81729a8b 100755 --- a/apps/files_pdfviewer/appinfo/info.xml +++ b/apps/files_pdfviewer/appinfo/info.xml @@ -3,7 +3,6 @@ files_pdfviewer PDF Viewer Inline PDF viewer (pdfjs-based) - 0.1 GPL Joan Creus 2 diff --git a/apps/files_pdfviewer/appinfo/version b/apps/files_pdfviewer/appinfo/version new file mode 100644 index 0000000000..ceab6e11ec --- /dev/null +++ b/apps/files_pdfviewer/appinfo/version @@ -0,0 +1 @@ +0.1 \ No newline at end of file diff --git a/apps/files_sharing/ajax/userautocomplete.php b/apps/files_sharing/ajax/userautocomplete.php index 9d971fb62a..38b673ee51 100644 --- a/apps/files_sharing/ajax/userautocomplete.php +++ b/apps/files_sharing/ajax/userautocomplete.php @@ -7,21 +7,23 @@ OC_JSON::checkLoggedIn(); OC_JSON::checkAppEnabled('files_sharing'); $users = array(); -$ocusers = OC_User::getUsers(); +$groups = array(); $self = OC_User::getUser(); -$groups = OC_Group::getUserGroups($self); +$userGroups = OC_Group::getUserGroups($self); $users[] = ""; -foreach ($ocusers as $user) { - if ($user != $self) { - $users[] = ""; +$groups[] = ""; +foreach ($userGroups as $group) { + $groupUsers = OC_Group::usersInGroup($group); + foreach ($groupUsers as $user) { + if ($user != $self) { + $users[] = ""; + } } + $groups[] = ""; } $users[] = ""; -$users[] = ""; -foreach ($groups as $group) { - $users[] = ""; -} -$users[] = ""; +$groups[] = ""; +$users = array_merge($users, $groups); OC_JSON::encodedPrint($users); ?> diff --git a/apps/files_sharing/appinfo/info.xml b/apps/files_sharing/appinfo/info.xml index abf847b448..490ffaca89 100644 --- a/apps/files_sharing/appinfo/info.xml +++ b/apps/files_sharing/appinfo/info.xml @@ -3,9 +3,11 @@ files_sharing Share Files File sharing between users - 0.1 AGPL Michael Gapczynski 2 + + + diff --git a/apps/files_sharing/appinfo/version b/apps/files_sharing/appinfo/version new file mode 100644 index 0000000000..ceab6e11ec --- /dev/null +++ b/apps/files_sharing/appinfo/version @@ -0,0 +1 @@ +0.1 \ No newline at end of file diff --git a/apps/files_sharing/get.php b/apps/files_sharing/get.php index 3a3db6dd38..3e42bf6a6c 100644 --- a/apps/files_sharing/get.php +++ b/apps/files_sharing/get.php @@ -31,7 +31,7 @@ if ($source !== false) { if ($i['type'] == 'file') { $fileinfo = pathinfo($i['name']); $i['basename'] = $fileinfo['filename']; - $i['extention'] = isset($fileinfo['extension']) ? ('.'.$fileinfo['extension']) : ''; + $i['extension'] = isset($fileinfo['extension']) ? ('.'.$fileinfo['extension']) : ''; } $i['directory'] = substr($i['directory'], $rootLength); if ($i['directory'] == "/") { @@ -62,6 +62,8 @@ if ($source !== false) { $tmpl->assign("fileList", $list->fetchPage()); $tmpl->assign("breadcrumb", $breadcrumbNav->fetchPage()); $tmpl->assign("readonly", true); + $tmpl->assign("allowZipDownload", false); + $tmpl->assign("dir", 'shared dir'); $tmpl->printPage(); } else { //get time mimetype and set the headers diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js index fc9e17c25c..54d749d833 100644 --- a/apps/files_sharing/js/share.js +++ b/apps/files_sharing/js/share.js @@ -2,8 +2,11 @@ $(document).ready(function() { var shared_status = {}; if (typeof FileActions !== 'undefined') { FileActions.register('all', 'Share', function(filename) { - if (scanFiles.scanning){return;}//workaround to prevent aditional http request block scanning feedback + if (scanFiles.scanning){return;}//workaround to prevent additional http request block scanning feedback var icon; + if (typeof filename == 'undefined') { + return false; + } var file = $('#dir').val()+'/'+filename; if(shared_status[file]) return shared_status[file].icon; @@ -145,7 +148,7 @@ $(document).ready(function() { data: data, success: function(token) { if (token) { - showPublicLink(token); + showPublicLink(token, source.substr(source.lastIndexOf('/'))); } } }); @@ -203,7 +206,7 @@ function createDropdown(filename, files) { if (users) { $.each(users, function(index, row) { if (row.uid_shared_with == 'public') { - showPublicLink(row.token); + showPublicLink(row.token, '/'+filename); } else if (isNaN(index)) { addUser(row.uid_shared_with, row.permissions, index.substr(0, index.lastIndexOf('-'))); } else { @@ -234,9 +237,9 @@ function addUser(uid_shared_with, permissions, parentFolder) { $(user).appendTo('#shared_list'); } -function showPublicLink(token) { +function showPublicLink(token, file) { $('#makelink').attr('checked', true); $('#link').data('token', token); - $('#link').val(parent.location.protocol+'//'+location.host+OC.linkTo('files_sharing','get.php')+'?token='+token); + $('#link').val(parent.location.protocol+'//'+location.host+OC.linkTo('files_sharing','get.php')+'?token='+token+'&f='+file); $('#link').show('blind'); } diff --git a/apps/files_sharing/lib_share.php b/apps/files_sharing/lib_share.php index 42739bdfba..673984f393 100644 --- a/apps/files_sharing/lib_share.php +++ b/apps/files_sharing/lib_share.php @@ -52,8 +52,18 @@ class OC_Share { // Remove the owner from the list of users in the group $uid_shared_with = array_diff($uid_shared_with, array($uid_owner)); } else if (OC_User::userExists($uid_shared_with)) { - $gid = null; - $uid_shared_with = array($uid_shared_with); + $userGroups = OC_Group::getUserGroups($uid_owner); + // Check if the user is in one of the owner's groups + foreach ($userGroups as $group) { + if ($inGroup = OC_Group::inGroup($uid_shared_with, $group)) { + $gid = null; + $uid_shared_with = array($uid_shared_with); + break; + } + } + if (!$inGroup) { + throw new Exception("You can't share with ".$uid_shared_with); + } } else { throw new Exception($uid_shared_with." is not a user"); } diff --git a/apps/files_texteditor/appinfo/info.xml b/apps/files_texteditor/appinfo/info.xml index da1cdba15d..83c057f38f 100644 --- a/apps/files_texteditor/appinfo/info.xml +++ b/apps/files_texteditor/appinfo/info.xml @@ -3,7 +3,6 @@ files_texteditor Text Editor Simple plain text editor based on Ace editor. - 0.3 AGPL Tom Needham 2 diff --git a/apps/files_texteditor/appinfo/version b/apps/files_texteditor/appinfo/version new file mode 100644 index 0000000000..1d71ef9744 --- /dev/null +++ b/apps/files_texteditor/appinfo/version @@ -0,0 +1 @@ +0.3 \ No newline at end of file diff --git a/apps/files_texteditor/css/style.css b/apps/files_texteditor/css/style.css index e260ab0dd4..6a4392a08e 100644 --- a/apps/files_texteditor/css/style.css +++ b/apps/files_texteditor/css/style.css @@ -5,7 +5,7 @@ left: 12.5em; } #editorwrapper{ - position: absoloute; + position: absolute; height: 0; width: 0; top: 41px; diff --git a/apps/files_texteditor/js/editor.js b/apps/files_texteditor/js/editor.js index 02d39b9843..7a47fba468 100644 --- a/apps/files_texteditor/js/editor.js +++ b/apps/files_texteditor/js/editor.js @@ -22,9 +22,11 @@ function setSyntaxMode(ext){ filetype["css"] = "css"; filetype["groovy"] = "groovy"; filetype["haxe"] = "hx"; + filetype["htm"] = "html"; filetype["html"] = "html"; filetype["java"] = "java"; filetype["js"] = "javascript"; + filetype["jsm"] = "javascript"; filetype["json"] = "json"; filetype["latex"] = "latex"; filetype["ly"] = "latex"; @@ -57,7 +59,7 @@ function setSyntaxMode(ext){ var SyntaxMode = require("ace/mode/"+filetype[ext]).Mode; window.aceEditor.getSession().setMode(new SyntaxMode()); }); - } + } } function showControls(filename,writeperms){ @@ -67,17 +69,16 @@ function showControls(filename,writeperms){ if(writeperms=="true"){ editorbarhtml += '
    '; } - editorbarhtml += '
    '; + editorbarhtml += '
    '; // Change breadcrumb classes $('#controls .last').removeClass('last'); $('#controls').append(editorbarhtml); $('#editorcontrols').fadeIn('slow'); } - + function bindControlEvents(){ - $("#editor_save").die('click',doFileSave).live('click',doFileSave); + $("#editor_save").die('click',doFileSave).live('click',doFileSave); $('#editor_close').die('click',hideFileEditor).live('click',hideFileEditor); - $('#gotolineval').die('keyup', goToLine).live('keyup', goToLine); $('#editorsearchval').die('keyup', doSearch).live('keyup', doSearch); $('#clearsearchbtn').die('click', resetSearch).live('click', resetSearch); $('#nextsearchbtn').die('click', nextSearchResult).live('click', nextSearchResult); @@ -89,19 +90,12 @@ function editorIsShown(){ return is_editor_shown; } -// Moves the editor view to the line number speificed in #gotolineval -function goToLine(){ - // Go to the line specified - window.aceEditor.gotoLine($('#gotolineval').val()); - -} - //resets the search function resetSearch(){ $('#editorsearchval').val(''); $('#nextsearchbtn').remove(); $('#clearsearchbtn').remove(); - window.aceEditor.gotoLine(0); + window.aceEditor.gotoLine(0); } // moves the cursor to the next search resukt @@ -109,10 +103,10 @@ function nextSearchResult(){ window.aceEditor.findNext(); } // Performs the initial search -function doSearch(){ +function doSearch(){ // check if search box empty? if($('#editorsearchval').val()==''){ - // Hide clear button + // Hide clear button window.aceEditor.gotoLine(0); $('#nextsearchbtn').remove(); $('#clearsearchbtn').remove(); @@ -127,7 +121,7 @@ function doSearch(){ caseSensitive: false, wholeWord: false, regExp: false - }); + }); // Show next and clear buttons // check if already there if($('#nextsearchbtn').length==0){ @@ -141,32 +135,40 @@ function doSearch(){ // Tries to save the file. function doFileSave(){ if(editorIsShown()){ - // Get file path - var path = $('#editor').attr('data-dir')+'/'+$('#editor').attr('data-filename'); - // Get original mtime - var mtime = $('#editor').attr('data-mtime'); - // Show saving spinner - $("#editor_save").die('click',doFileSave); - $('#save_result').remove(); - $('#editor_save').text(t('files_texteditor','Saving...')); - // Get the data - var filecontents = window.aceEditor.getSession().getValue(); - // Send the data - $.post(OC.filePath('files_texteditor','ajax','savefile.php'), { filecontents: filecontents, path: path, mtime: mtime },function(jsondata){ - if(jsondata.status!='success'){ - // Save failed - $('#editor_save').text(t('files_texteditor','Save')); - $('#editor_save').after('

    Failed to save file

    '); - $("#editor_save").live('click',doFileSave); - } else { - // Save OK - // Update mtime - $('#editor').attr('data-mtime',jsondata.data.mtime); - $('#editor_save').text(t('files_texteditor','Save')); - $("#editor_save").live('click',doFileSave); - } - },'json'); + // Changed contents? + if($('#editor').attr('data-edited')=='true'){ + // Get file path + var path = $('#editor').attr('data-dir')+'/'+$('#editor').attr('data-filename'); + // Get original mtime + var mtime = $('#editor').attr('data-mtime'); + // Show saving spinner + $("#editor_save").die('click',doFileSave); + $('#save_result').remove(); + $('#editor_save').text(t('files_texteditor','Saving...')); + // Get the data + var filecontents = window.aceEditor.getSession().getValue(); + // Send the data + $.post(OC.filePath('files_texteditor','ajax','savefile.php'), { filecontents: filecontents, path: path, mtime: mtime },function(jsondata){ + if(jsondata.status!='success'){ + // Save failed + $('#editor_save').text(t('files_texteditor','Save')); + $('#editor_save').after('

    Failed to save file

    '); + $("#editor_save").live('click',doFileSave); + } else { + // Save OK + // Update mtime + $('#editor').attr('data-mtime',jsondata.data.mtime); + $('#editor_save').text(t('files_texteditor','Save')); + $("#editor_save").live('click',doFileSave); + // Update titles + $('#editor').attr('data-edited', 'false'); + $('#breadcrumb_file').text($('#editor').attr('data-filename')); + document.title = $('#editor').attr('data-filename')+' - ownCloud'; + } + },'json'); + } } + giveEditorFocus(); }; // Gives the editor focus @@ -176,6 +178,8 @@ function giveEditorFocus(){ // Loads the file editor. Accepts two parameters, dir and filename. function showFileEditor(dir,filename){ + // Delete any old editors + $('#editor').remove(); if(!editorIsShown()){ // Loads the file editor and display it. $('#content').append('
    '); @@ -192,10 +196,11 @@ function showFileEditor(dir,filename){ // Show the control bar showControls(filename,result.data.write); // Update document title - document.title = filename; + document.title = filename+' - ownCloud'; $('#editor').text(result.data.filecontents); $('#editor').attr('data-dir', dir); $('#editor').attr('data-filename', filename); + $('#editor').attr('data-edited', 'false'); window.aceEditor = ace.edit("editor"); aceEditor.setShowPrintMargin(false); aceEditor.getSession().setUseWrapMode(true); @@ -207,10 +212,17 @@ function showFileEditor(dir,filename){ OC.addScript('files_texteditor','aceeditor/theme-clouds', function(){ window.aceEditor.setTheme("ace/theme/clouds"); }); + window.aceEditor.getSession().on('change', function(){ + if($('#editor').attr('data-edited')!='true'){ + $('#editor').attr('data-edited', 'true'); + $('#breadcrumb_file').text($('#breadcrumb_file').text()+' *'); + document.title = $('#editor').attr('data-filename')+' * - ownCloud'; + } + }); }); } else { // Failed to get the file. - alert(result.data.message); + OC.dialogs.alert(result.data.message, t('files_texteditor','An error occurred!')); } // End success } @@ -222,37 +234,54 @@ function showFileEditor(dir,filename){ // Fades out the editor. function hideFileEditor(){ - // Fades out editor controls - $('#editorcontrols').fadeOut('slow',function(){ - $(this).remove(); - $(".crumb:last").addClass('last'); - }); - // Fade out editor - $('#editor').fadeOut('slow', function(){ - $(this).remove(); - // Reset document title - document.title = "ownCloud"; - var editorhtml = '
    '; - $('table').after(editorhtml); - $('.actions,#file_access_panel').fadeIn('slow'); - $('table').fadeIn('slow'); - }); - is_editor_shown = false; + if($('#editor').attr('data-edited') == 'true'){ + // Hide, not remove + $('#editorcontrols').fadeOut('slow',function(){ + // Check if there is a folder in the breadcrumb + if($('.crumb.ui-droppable').length){ + $('.crumb.ui-droppable:last').addClass('last'); + } + }); + // Fade out editor + $('#editor').fadeOut('slow', function(){ + // Reset document title + document.title = "ownCloud"; + $('.actions,#file_access_panel').fadeIn('slow'); + $('table').fadeIn('slow'); + }); + $('#notification').text(t('files_texteditor','There were unsaved changes, click here to go back')); + $('#notification').data('reopeneditor',true); + $('#notification').fadeIn(); + is_editor_shown = false; + } else { + // Remove editor + $('#editorcontrols').fadeOut('slow',function(){ + $(this).remove(); + $(".crumb:last").addClass('last'); + }); + // Fade out editor + $('#editor').fadeOut('slow', function(){ + $(this).remove(); + // Reset document title + document.title = "ownCloud"; + $('.actions,#file_access_panel').fadeIn('slow'); + $('table').fadeIn('slow'); + }); + is_editor_shown = false; + } } -// Keyboard Shortcuts -var ctrlBtn = false; +// Reopens the last document +function reopenEditor(){ + $('.actions,#file_action_panel').fadeOut('slow'); + $('table').fadeOut('slow', function(){ + $('#controls .last').not('#breadcrumb_file').removeClass('last'); + $('#editor').fadeIn('fast'); + $('#editorcontrols').fadeIn('fast', function(){ -// TODO fix detection of ctrl keyup -// returns true if ctrl+s or cmd+s is being pressed -function checkForSaveKeyPress(e){ - if(e.which == 17 || e.which == 91) ctrlBtn=true; - if(e.which == 83 && ctrlBtn == true) { - e.preventDefault(); - $('#editor_save').trigger('click'); - return false; - - } + }); + }); + is_editor_shown = true; } // resizes the editor window @@ -285,6 +314,11 @@ $(document).ready(function(){ // Binds the file save and close editor events, and gotoline button bindControlEvents(); $('#editor').remove(); - // Binds the save keyboard shortcut events - //$(document).unbind('keydown').bind('keydown',checkForSaveKeyPress); + $('#notification').click(function(){ + if($('#notification').data('reopeneditor')) + { + reopenEditor(); + } + $('#notification').fadeOut(); + }); }); diff --git a/apps/files_versioning/ajax/gethead.php b/apps/files_versioning/ajax/gethead.php new file mode 100644 index 0000000000..cc93b7a1d1 --- /dev/null +++ b/apps/files_versioning/ajax/gethead.php @@ -0,0 +1,12 @@ + $head)); diff --git a/apps/files_versioning/ajax/sethead.php b/apps/files_versioning/ajax/sethead.php new file mode 100644 index 0000000000..d1b2df9b00 --- /dev/null +++ b/apps/files_versioning/ajax/sethead.php @@ -0,0 +1,14 @@ + 10, + 'id' => 'files_versioning', + 'name' => 'Versioning and Backup' )); + +// Include stylesheets for the settings page +OC_Util::addStyle( 'files_versioning', 'settings' ); +OC_Util::addScript('files_versioning','settings'); + +// Register a settings section in the Admin > Personal page +OC_APP::registerPersonal('files_versioning','settings'); diff --git a/apps/files_versioning/appinfo/info.xml b/apps/files_versioning/appinfo/info.xml new file mode 100644 index 0000000000..b9f56f674a --- /dev/null +++ b/apps/files_versioning/appinfo/info.xml @@ -0,0 +1,12 @@ + + + files_versioning + Versioning and Backup + GPLv2 + Craig Roberts + 3 + Versions files using Git repositories, providing a simple backup facility. Currently in *beta* and explicitly without warranty of any kind. + + + + diff --git a/apps/files_versioning/appinfo/version b/apps/files_versioning/appinfo/version new file mode 100644 index 0000000000..afaf360d37 --- /dev/null +++ b/apps/files_versioning/appinfo/version @@ -0,0 +1 @@ +1.0.0 \ No newline at end of file diff --git a/apps/files_versioning/css/settings.css b/apps/files_versioning/css/settings.css new file mode 100644 index 0000000000..afe2cd5508 --- /dev/null +++ b/apps/files_versioning/css/settings.css @@ -0,0 +1,3 @@ +#file_versioning_commit_chzn { + width: 15em; +} diff --git a/apps/files_versioning/js/settings.js b/apps/files_versioning/js/settings.js new file mode 100644 index 0000000000..8dd13bac03 --- /dev/null +++ b/apps/files_versioning/js/settings.js @@ -0,0 +1,25 @@ +$(document).ready(function(){ + $('#file_versioning_head').chosen(); + + $.getJSON(OC.filePath('files_versioning', 'ajax', 'gethead.php'), function(jsondata, status) { + + if (jsondata.head == 'HEAD') { + // Most recent commit, do nothing + } else { + $("#file_versioning_head").val(jsondata.head); + // Trigger the chosen update call + // See http://harvesthq.github.com/chosen/ + $("#file_versioning_head").trigger("liszt:updated"); + } + }); + + $('#file_versioning_head').change(function() { + + var data = $(this).serialize(); + $.post( OC.filePath('files_versioning', 'ajax', 'sethead.php'), data, function(data){ + if(data == 'error'){ + console.log('Saving new HEAD failed'); + } + }); + }); +}); diff --git a/apps/files_versioning/lib_granite.php b/apps/files_versioning/lib_granite.php new file mode 100644 index 0000000000..571e5cea63 --- /dev/null +++ b/apps/files_versioning/lib_granite.php @@ -0,0 +1,12 @@ +head(); +for ($i = 0; $i < 50; $i++) { + $commits[] = $commit; + $parents = $commit->parents(); + if (count($parents) > 0) { + $parent = $parents[0]; + } else { + break; + } + + $commit = $repository->factory('commit', $parent); +} + +$tmpl = new OC_Template( 'files_versioning', 'settings'); +$tmpl->assign('commits', $commits); +return $tmpl->fetchPage(); diff --git a/apps/files_versioning/templates/settings.php b/apps/files_versioning/templates/settings.php new file mode 100644 index 0000000000..17f4cc7f77 --- /dev/null +++ b/apps/files_versioning/templates/settings.php @@ -0,0 +1,12 @@ +
    + Versioning and Backup
    +

    Please note: Backing up large files (around 16MB+) will cause your backup history to grow very large, very quickly.

    + + +
    diff --git a/apps/files_versioning/versionstorage.php b/apps/files_versioning/versionstorage.php new file mode 100644 index 0000000000..d083e623df --- /dev/null +++ b/apps/files_versioning/versionstorage.php @@ -0,0 +1,386 @@ +. + */ + +// Include Granite +require_once('lib_granite.php'); + +// Create a top-level 'Backup' directory if it does not already exist +$user = OC_User::getUser(); +if (OC_Filesystem::$loaded and !OC_Filesystem::is_dir('/Backup')) { + OC_Filesystem::mkdir('/Backup'); + OC_Preferences::setValue(OC_User::getUser(), 'files_versioning', 'head', 'HEAD'); +} + +// Generate the repository path (currently using 'full' repositories, as opposed to bare ones) +$repo_path = DIRECTORY_SEPARATOR + . OC_User::getUser() + . DIRECTORY_SEPARATOR + . 'files' + . DIRECTORY_SEPARATOR + . 'Backup'; + +// Mount the 'Backup' folder using the versioned storage provider below +OC_Filesystem::mount('OC_Filestorage_Versioned', array('repo'=>$repo_path), $repo_path . DIRECTORY_SEPARATOR); + +class OC_Filestorage_Versioned extends OC_Filestorage { + + /** + * Holds an instance of Granite\Git\Repository + */ + protected $repo; + + /** + * Constructs a new OC_Filestorage_Versioned instance, expects an associative + * array with a `repo` key set to the path of the repository's `.git` folder + * + * @param array $parameters An array containing the key `repo` pointing to the + * repository path. + */ + public function __construct($parameters) { + // Get the full path to the repository folder + $path = OC_Config::getValue('datadirectory', OC::$SERVERROOT.'/data') + . $parameters['repo'] + . DIRECTORY_SEPARATOR + . '.git' + . DIRECTORY_SEPARATOR; + + try { + // Attempt to load the repository + $this->repo = new Granite\Git\Repository($path); + } catch (InvalidArgumentException $e) { + // $path is not a valid Git repository, we must create one + Granite\Git\Repository::init($path); + + // Load the newly-initialised repository + $this->repo = new Granite\Git\Repository($path); + + /** + * Create an initial commit with a README file + * FIXME: This functionality should be transferred to the Granite library + */ + $blob = new Granite\Git\Blob($this->repo->path()); + $blob->content('Your Backup directory is now ready for use.'); + + // Create a new tree to hold the README file + $tree = $this->repo->factory('tree'); + // Create a tree node to represent the README blob + $tree_node = new Granite\Git\Tree\Node('README', '100644', $blob->sha()); + $tree->nodes(array($tree_node->name() => $tree_node)); + + // Create an initial commit + $commit = new Granite\Git\Commit($this->repo->path()); + $user_string = OC_User::getUser() . ' ' . time() . ' +0000'; + $commit->author($user_string); + $commit->committer($user_string); + $commit->message('Initial commit'); + $commit->tree($tree); + + // Write it all to disk + $blob->write(); + $tree->write(); + $commit->write(); + + // Update the HEAD for the 'master' branch + $this->repo->head('master', $commit->sha()); + } + + // Update the class pointer to the HEAD + $head = OC_Preferences::getValue(OC_User::getUser(), 'files_versioning', 'head', 'HEAD'); + + // Load the most recent commit if the preference is not set + if ($head == 'HEAD') { + $this->head = $this->repo->head()->sha(); + } else { + $this->head = $head; + } + } + + public function mkdir($path) { + if (mkdir("versioned:/{$this->repo->path()}$path#{$this->head}")) { + $this->head = $this->repo->head()->sha(); + OC_Preferences::setValue(OC_User::getUser(), 'files_versioning', 'head', $head); + return true; + } + + return false; + } + + public function rmdir($path) { + + } + + /** + * Returns a directory handle to the requested path, or FALSE on failure + * + * @param string $path The directory path to open + * + * @return boolean|resource A directory handle, or FALSE on failure + */ + public function opendir($path) { + return opendir("versioned:/{$this->repo->path()}$path#{$this->head}"); + } + + /** + * Returns TRUE if $path is a directory, or FALSE if not + * + * @param string $path The path to check + * + * @return boolean + */ + public function is_dir($path) { + return $this->filetype($path) == 'dir'; + } + + /** + * Returns TRUE if $path is a file, or FALSE if not + * + * @param string $path The path to check + * + * @return boolean + */ + public function is_file($path) { + return $this->filetype($path) == 'file'; + } + + public function stat($path) + { + return stat("versioned:/{$this->repo->path()}$path#{$this->head}"); + } + + /** + * Returns the strings 'dir' or 'file', depending on the type of $path + * + * @param string $path The path to check + * + * @return string Returns 'dir' if a directory, 'file' otherwise + */ + public function filetype($path) { + if ($path == "" || $path == "/") { + return 'dir'; + } else { + if (substr($path, -1) == '/') { + $path = substr($path, 0, -1); + } + + $node = $this->tree_search($this->repo, $this->repo->factory('commit', $this->head)->tree(), $path); + + // Does it exist, or is it new? + if ($node == null) { + // New file + return 'file'; + } else { + // Is it a tree? + try { + $this->repo->factory('tree', $node); + return 'dir'; + } catch (InvalidArgumentException $e) { + // Nope, must be a blob + return 'file'; + } + } + } + } + + public function filesize($path) { + return filesize("versioned:/{$this->repo->path()}$path#{$this->head}"); + } + + /** + * Returns a boolean value representing whether $path is readable + * + * @param string $path The path to check + *( + * @return boolean Whether or not the path is readable + */ + public function is_readable($path) { + return true; + } + + /** + * Returns a boolean value representing whether $path is writable + * + * @param string $path The path to check + *( + * @return boolean Whether or not the path is writable + */ + public function is_writable($path) { + + $head = OC_Preferences::getValue(OC_User::getUser(), 'files_versioning', 'head', 'HEAD'); + if ($head !== 'HEAD' && $head !== $this->repo->head()->sha()) { + // Cannot modify previous commits + return false; + } + return true; + } + + /** + * Returns a boolean value representing whether $path exists + * + * @param string $path The path to check + *( + * @return boolean Whether or not the path exists + */ + public function file_exists($path) { + return file_exists("versioned:/{$this->repo->path()}$path#{$this->head}"); + } + + /** + * Returns an integer value representing the inode change time + * (NOT IMPLEMENTED) + * + * @param string $path The path to check + *( + * @return int Timestamp of the last inode change + */ + public function filectime($path) { + return -1; + } + + /** + * Returns an integer value representing the file modification time + * + * @param string $path The path to check + *( + * @return int Timestamp of the last file modification + */ + public function filemtime($path) { + return filemtime("versioned:/{$this->repo->path()}$path#{$this->head}"); + } + + public function file_get_contents($path) { + return file_get_contents("versioned:/{$this->repo->path()}$path#{$this->head}"); + } + + public function file_put_contents($path, $data) { + $success = file_put_contents("versioned:/{$this->repo->path()}$path#{$this->head}", $data); + if ($success !== false) { + // Update the HEAD in the preferences + OC_Preferences::setValue(OC_User::getUser(), 'files_versioning', 'head', $this->repo->head()->sha()); + return $success; + } + + return false; + } + + public function unlink($path) { + + } + + public function rename($path1, $path2) { + + } + + public function copy($path1, $path2) { + + } + + public function fopen($path, $mode) { + return fopen("versioned:/{$this->repo->path()}$path#{$this->head}", $mode); + } + + public function getMimeType($path) { + if ($this->filetype($path) == 'dir') { + return 'httpd/unix-directory'; + } elseif ($this->filesize($path) == 0) { + // File's empty, returning text/plain allows opening in the web editor + return 'text/plain'; + } else { + $finfo = new finfo(FILEINFO_MIME_TYPE); + /** + * We need to represent the repository path, the file path, and the + * revision, which can be simply achieved with a convention of using + * `.git` in the repository directory (bare or not) and the '#part' + * segment of a URL to specify the revision. For example + * + * versioned://var/www/myrepo.git/docs/README.md#HEAD ('bare' repo) + * versioned://var/www/myrepo/.git/docs/README.md#HEAD ('full' repo) + * versioned://var/www/myrepo/.git/docs/README.md#6a8f...8a54 ('full' repo and SHA-1 commit ID) + */ + $mime = $finfo->buffer(file_get_contents("versioned:/{$this->repo->path()}$path#{$this->head}")); + return $mime; + } + } + + /** + * Generates a hash based on the file contents + * + * @param string $type The hashing algorithm to use (e.g. 'md5', 'sha256', etc.) + * @param string $path The file to be hashed + * @param boolean $raw Outputs binary data if true, lowercase hex digits otherwise + * + * @return string Hashed string representing the file contents + */ + public function hash($type, $path, $raw) { + return hash($type, file_get_contents($path), $raw); + } + + public function free_space($path) { + } + + public function search($query) { + + } + + public function touch($path, $mtime=null) { + + } + + + public function getLocalFile($path) { + } + + /** + * Recursively searches a tree for a path, returning FALSE if is not found + * or an SHA-1 id if it is found. + * + * @param string $repo The repository containing the tree object + * @param string $tree The tree object to search + * @param string $path The path to search for (relative to the tree) + * @param int $depth The depth of the current search (for recursion) + * + * @return string|boolean The SHA-1 id of the sub-tree + */ + private function tree_search($repo, $tree, $path, $depth = 0) + { + $paths = array_values(explode(DIRECTORY_SEPARATOR, $path)); + + $current_path = $paths[$depth]; + + $nodes = $tree->nodes(); + foreach ($nodes as $node) { + if ($node->name() == $current_path) { + + if (count($paths)-1 == $depth) { + // Stop, found it + return $node->sha(); + } + + // Recurse if necessary + if ($node->isDirectory()) { + $tree = $this->repo->factory('tree', $node->sha()); + return $this->tree_search($repo, $tree, $path, $depth + 1); + } + } + } + + return false; + } + +} diff --git a/apps/files_versioning/versionwrapper.php b/apps/files_versioning/versionwrapper.php new file mode 100644 index 0000000000..b83a4fd3b2 --- /dev/null +++ b/apps/files_versioning/versionwrapper.php @@ -0,0 +1,686 @@ +tree); + return true; + } + + /** + * Open directory handle + */ + public function dir_opendir($path, $options) { + // Parse the URL into a repository directory, file path and commit ID + list($this->repo, $repo_file, $this->commit) = $this->parse_url($path); + + if ($repo_file == '' || $repo_file == '/') { + // Set the tree property for the future `readdir()` etc. calls + $this->tree = array_values($this->commit->tree()->nodes()); + return true; + } elseif ($this->tree_search($this->repo, $this->commit->tree(), $repo_file) !== false) { + // Something exists at this path, is it a directory though? + try { + $tree = $this->repo->factory( + 'tree', + $this->tree_search($this->repo, $this->commit->tree(), $repo_file) + ); + $this->tree = array_values($tree->nodes()); + return true; + } catch (InvalidArgumentException $e) { + // Trying to call `opendir()` on a file, return false below + } + } + + // Unable to find the directory, return false + return false; + } + + /** + * Read entry from directory handle + */ + public function dir_readdir() { + return isset($this->tree[$this->dir_position]) + ? $this->tree[$this->dir_position++]->name() + : false; + } + + /** + * Rewind directory handle + */ + public function dir_rewinddir() { + $this->dir_position = 0; + } + + /** + * Create a directory + * Git doesn't track empty directories, so a ".empty" file is added instead + */ + public function mkdir($path, $mode, $options) { + // Parse the URL into a repository directory, file path and commit ID + list($this->repo, $repo_file, $this->commit) = $this->parse_url($path); + + // Create an empty file for Git + $empty = new Granite\Git\Blob($this->repo->path()); + $empty->content(''); + $empty->write(); + + if (dirname($repo_file) == '.') { + // Adding a new directory to the root tree + $tree = $this->repo->head()->tree(); + } else { + $tree = $this->repo->factory('tree', $this->tree_search( + $this->repo, $this->repo->head()->tree(), dirname($repo_file) + ) + ); + } + + // Create our new tree, with our empty file + $dir = $this->repo->factory('tree'); + $nodes = array(); + $nodes[self::EMPTYFILE] = new Granite\Git\Tree\Node(self::EMPTYFILE, '100644', $empty->sha()); + $dir->nodes($nodes); + $dir->write(); + + // Add our new tree to its parent + $nodes = $tree->nodes(); + $nodes[basename($repo_file)] = new Granite\Git\Tree\Node(basename($repo_file), '040000', $dir->sha()); + $tree->nodes($nodes); + $tree->write(); + + // We need to recursively update each parent tree, since they are all + // hashed and the changes will cascade back up the chain + + // So, we're currently at the bottom-most directory + $current_dir = dirname($repo_file); + $previous_tree = $tree; + + if ($current_dir !== '.') { + do { + // Determine the parent directory + $previous_dir = $current_dir; + $current_dir = dirname($current_dir); + + $current_tree = $current_dir !== '.' + ? $this->repo->factory( + 'tree', $this->tree_search( + $this->repo, + $this->repo->head()->tree(), + $current_dir + ) + ) + : $this->repo->head()->tree(); + + $current_nodes = $current_tree->nodes(); + $current_nodes[basename($previous_dir)] = new Granite\Git\Tree\Node( + basename($previous_dir), '040000', $previous_tree->sha() + ); + $current_tree->nodes($current_nodes); + $current_tree->write(); + + $previous_tree = $current_tree; + } while ($current_dir !== '.'); + + $tree = $previous_tree; + } + + // Create a new commit to represent this write + $commit = $this->repo->factory('commit'); + $username = OC_User::getUser(); + $user_string = $username . ' ' . time() . ' +0000'; + $commit->author($user_string); + $commit->committer($user_string); + $commit->message("$username created the `$repo_file` directory, " . date('d F Y H:i', time()) . '.'); + $commit->parents(array($this->repo->head()->sha())); + $commit->tree($tree); + + // Write it to disk + $commit->write(); + + // Update the HEAD for the 'master' branch + $this->repo->head('master', $commit->sha()); + + return true; + } + + /** + * Renames a file or directory + */ + public function rename($path_from, $path_to) { + + } + + /** + * Removes a directory + */ + public function rmdir($path, $options) { + + } + + /** + * Retrieve the underlaying resource (NOT IMPLEMENTED) + */ + public function stream_cast($cast_as) { + return false; + } + + /** + * Close a resource + */ + public function stream_close() { + unset($this->blob); + return true; + } + + /** + * Tests for end-of-file on a file pointer + */ + public function stream_eof() { + return !($this->file_position < strlen($this->blob)); + } + + /** + * Flushes the output (NOT IMPLEMENTED) + */ + public function stream_flush() { + return false; + } + + /** + * Advisory file locking (NOT IMPLEMENTED) + */ + public function stream_lock($operation) { + return false; + } + + /** + * Change stream options (NOT IMPLEMENTED) + * Called in response to `chgrp()`, `chown()`, `chmod()` and `touch()` + */ + public function stream_metadata($path, $option, $var) { + return false; + } + + /** + * Opens file or URL + */ + public function stream_open($path, $mode, $options, &$opened_path) { + // Store the path, so we can use it later in `stream_write()` if necessary + $this->path = $path; + // Parse the URL into a repository directory, file path and commit ID + list($this->repo, $repo_file, $this->commit) = $this->parse_url($path); + + $file = $this->tree_search($this->repo, $this->commit->tree(), $repo_file); + if ($file !== false) { + try { + $this->blob = $this->repo->factory('blob', $file)->content(); + return true; + } catch (InvalidArgumentException $e) { + // Trying to open a directory, return false below + } + } elseif ($mode !== 'r') { + // All other modes allow opening for reading and writing, clearly + // some 'write' files may not exist yet... + return true; + } + + // File could not be found or is not actually a file + return false; + } + + /** + * Read from stream + */ + public function stream_read($count) { + // Fetch the remaining set of bytes + $bytes = substr($this->blob, $this->file_position, $count); + + // If EOF or empty string, return false + if ($bytes == '' || $bytes == false) { + return false; + } + + // If $count does not extend past EOF, add $count to stream offset + if ($this->file_position + $count < strlen($this->blob)) { + $this->file_position += $count; + } else { + // Otherwise return all remaining bytes + $this->file_position = strlen($this->blob); + } + + return $bytes; + } + + /** + * Seeks to specific location in a stream + */ + public function stream_seek($offset, $whence = SEEK_SET) { + $new_offset = false; + + switch ($whence) + { + case SEEK_SET: + $new_offset = $offset; + break; + case SEEK_CUR: + $new_offset = $this->file_position += $offset; + break; + case SEEK_END: + $new_offset = strlen($this->blob) + $offset; + break; + } + + $this->file_position = $offset; + + return ($new_offset !== false); + } + + /** + * Change stream options (NOT IMPLEMENTED) + */ + public function stream_set_option($option, $arg1, $arg2) { + return false; + } + + /** + * Retrieve information about a file resource (NOT IMPLEMENTED) + */ + public function stream_stat() { + + } + + /** + * Retrieve the current position of a stream + */ + public function stream_tell() { + return $this->file_position; + } + + /** + * Truncate stream + */ + public function stream_truncate($new_size) { + + } + + /** + * Write to stream + * FIXME: Could use heavy refactoring + */ + public function stream_write($data) { + /** + * FIXME: This also needs to be added to Granite, in the form of `add()`, + * `rm()` and `commit()` calls + */ + + // Parse the URL into a repository directory, file path and commit ID + list($this->repo, $repo_file, $this->commit) = $this->parse_url($this->path); + + $node = $this->tree_search($this->repo, $this->commit->tree(), $repo_file); + + if ($node !== false) { + // File already exists, attempting modification of existing tree + try { + $this->repo->factory('blob', $node); + + // Create our new blob with the provided $data + $blob = $this->repo->factory('blob'); + $blob->content($data); + $blob->write(); + + // We know the tree exists, so strip the filename from the path and + // find it... + + if (dirname($repo_file) == '.' || dirname($repo_file) == '') { + // Root directory + $tree = $this->repo->head()->tree(); + } else { + // Sub-directory + $tree = $this->repo->factory('tree', $this->tree_search( + $this->repo, + $this->repo->head()->tree(), + dirname($repo_file) + ) + ); + } + + // Replace the old blob with our newly modified one + $tree_nodes = $tree->nodes(); + $tree_nodes[basename($repo_file)] = new Granite\Git\Tree\Node( + basename($repo_file), '100644', $blob->sha() + ); + $tree->nodes($tree_nodes); + $tree->write(); + + // We need to recursively update each parent tree, since they are all + // hashed and the changes will cascade back up the chain + + // So, we're currently at the bottom-most directory + $current_dir = dirname($repo_file); + $previous_tree = $tree; + + if ($current_dir !== '.') { + do { + // Determine the parent directory + $previous_dir = $current_dir; + $current_dir = dirname($current_dir); + + $current_tree = $current_dir !== '.' + ? $this->repo->factory( + 'tree', $this->tree_search( + $this->repo, + $this->repo->head()->tree(), + $current_dir + ) + ) + : $this->repo->head()->tree(); + + $current_nodes = $current_tree->nodes(); + $current_nodes[basename($previous_dir)] = new Granite\Git\Tree\Node( + basename($previous_dir), '040000', $previous_tree->sha() + ); + $current_tree->nodes($current_nodes); + $current_tree->write(); + + $previous_tree = $current_tree; + } while ($current_dir !== '.'); + } + + // Create a new commit to represent this write + $commit = $this->repo->factory('commit'); + $username = OC_User::getUser(); + $user_string = $username . ' ' . time() . ' +0000'; + $commit->author($user_string); + $commit->committer($user_string); + $commit->message("$username modified the `$repo_file` file, " . date('d F Y H:i', time()) . '.'); + $commit->parents(array($this->repo->head()->sha())); + $commit->tree($previous_tree); + + // Write it to disk + $commit->write(); + + // Update the HEAD for the 'master' branch + $this->repo->head('master', $commit->sha()); + + // If we made it this far, write was successful - update the stream + // position and return the number of bytes written + $this->file_position += strlen($data); + return strlen($data); + + } catch (InvalidArgumentException $e) { + // Attempting to write to a directory or other error, fail + return 0; + } + } else { + // File does not exist, needs to be created + + // Create our new blob with the provided $data + $blob = $this->repo->factory('blob'); + $blob->content($data); + $blob->write(); + + if (dirname($repo_file) == '.') { + // Trying to add a new file to the root tree, nice and easy + $tree = $this->repo->head()->tree(); + $tree_nodes = $tree->nodes(); + $tree_nodes[basename($repo_file)] = new Granite\Git\Tree\Node( + basename($repo_file), '100644', $blob->sha() + ); + $tree->nodes($tree_nodes); + $tree->write(); + } else { + // Trying to add a new file to a subdirectory, try and find it + $tree = $this->repo->factory('tree', $this->tree_search( + $this->repo, $this->repo->head()->tree(), dirname($repo_file) + ) + ); + + // Add the blob to the tree + $nodes = $tree->nodes(); + $nodes[basename($repo_file)] = new Granite\Git\Tree\Node( + basename($repo_file), '100644', $blob->sha() + ); + $tree->nodes($nodes); + $tree->write(); + + // We need to recursively update each parent tree, since they are all + // hashed and the changes will cascade back up the chain + + // So, we're currently at the bottom-most directory + $current_dir = dirname($repo_file); + $previous_tree = $tree; + + if ($current_dir !== '.') { + do { + // Determine the parent directory + $previous_dir = $current_dir; + $current_dir = dirname($current_dir); + + $current_tree = $current_dir !== '.' + ? $this->repo->factory( + 'tree', $this->tree_search( + $this->repo, + $this->repo->head()->tree(), + $current_dir + ) + ) + : $this->repo->head()->tree(); + + $current_nodes = $current_tree->nodes(); + $current_nodes[basename($previous_dir)] = new Granite\Git\Tree\Node( + basename($previous_dir), '040000', $previous_tree->sha() + ); + $current_tree->nodes($current_nodes); + $current_tree->write(); + + $previous_tree = $current_tree; + } while ($current_dir !== '.'); + + $tree = $previous_tree; + } + } + + // Create a new commit to represent this write + $commit = $this->repo->factory('commit'); + $username = OC_User::getUser(); + $user_string = $username . ' ' . time() . ' +0000'; + $commit->author($user_string); + $commit->committer($user_string); + $commit->message("$username created the `$repo_file` file, " . date('d F Y H:i', time()) . '.'); + $commit->parents(array($this->repo->head()->sha())); + $commit->tree($tree); // Top-level tree (NOT the newly modified tree) + + // Write it to disk + $commit->write(); + + // Update the HEAD for the 'master' branch + $this->repo->head('master', $commit->sha()); + + // If we made it this far, write was successful - update the stream + // position and return the number of bytes written + $this->file_position += strlen($data); + return strlen($data); + } + + // Write failed + return 0; + } + + /** + * Delete a file + */ + public function unlink($path) { + + } + + /** + * Retrieve information about a file + */ + public function url_stat($path, $flags) { + // Parse the URL into a repository directory, file path and commit ID + list($this->repo, $repo_file, $this->commit) = $this->parse_url($path); + + $node = $this->tree_search($this->repo, $this->commit->tree(), $repo_file); + + if ($node == false && $this->commit->sha() == $this->repo->head()->sha()) { + // A new file - no information available + $size = 0; + $mtime = -1; + } else { + + // Is it a directory? + try { + $this->repo->factory('tree', $node); + $size = 4096; // FIXME + } catch (InvalidArgumentException $e) { + // Must be a file + $size = strlen(file_get_contents($path)); + } + + // Parse the timestamp from the commit message + preg_match('/[0-9]{10}+/', $this->commit->committer(), $matches); + $mtime = $matches[0]; + } + + $stat["dev"] = ""; + $stat["ino"] = ""; + $stat["mode"] = ""; + $stat["nlink"] = ""; + $stat["uid"] = ""; + $stat["gid"] = ""; + $stat["rdev"] = ""; + $stat["size"] = $size; + $stat["atime"] = $mtime; + $stat["mtime"] = $mtime; + $stat["ctime"] = $mtime; + $stat["blksize"] = ""; + $stat["blocks"] = ""; + + return $stat; + } + + /** + * Debug function for development purposes + */ + private function debug($message, $level = OC_Log::DEBUG) + { + if ($this->debug) { + OC_Log::write('files_versioning', $message, $level); + } + } + + /** + * Parses a URL of the form: + * `versioned://path/to/git/repository/.git/path/to/file#SHA-1-commit-id` + * FIXME: Will throw an InvalidArgumentException if $path is invaid + * + * @param string $path The path to parse + * + * @return array An array containing an instance of Granite\Git\Repository, + * the file path, and an instance of Granite\Git\Commit + * @throws InvalidArgumentException If the repository cannot be loaded + */ + private function parse_url($path) + { + preg_match('/\/([A-Za-z0-9\/]+\.git\/)([A-Za-z0-9\/\.\/]*)(#([A-Fa-f0-9]+))*/', $path, $matches); + + // Load up the repo + $repo = new \Granite\Git\Repository($matches[1]); + // Parse the filename (stripping any trailing slashes) + $repo_file = $matches[2]; + if (substr($repo_file, -1) == '/') { + $repo_file = substr($repo_file, 0, -1); + } + + // Default to HEAD if no commit is provided + $repo_commit = isset($matches[4]) + ? $matches[4] + : $repo->head()->sha(); + + // Load the relevant commit + $commit = $repo->factory('commit', $repo_commit); + + return array($repo, $repo_file, $commit); + } + + /** + * Recursively searches a tree for a path, returning FALSE if is not found + * or an SHA-1 id if it is found. + * + * @param string $repo The repository containing the tree object + * @param string $tree The tree object to search + * @param string $path The path to search for (relative to the tree) + * @param int $depth The depth of the current search (for recursion) + * + * @return string|boolean The SHA-1 id of the sub-tree + */ + private function tree_search($repo, $tree, $path, $depth = 0) + { + $paths = array_values(explode(DIRECTORY_SEPARATOR, $path)); + + $current_path = $paths[$depth]; + + $nodes = $tree->nodes(); + foreach ($nodes as $node) { + if ($node->name() == $current_path) { + + if (count($paths)-1 == $depth) { + // Stop, found it + return $node->sha(); + } + + // Recurse if necessary + if ($node->isDirectory()) { + $tree = $this->repo->factory('tree', $node->sha()); + return $this->tree_search($repo, $tree, $path, $depth + 1); + } + } + } + + return false; + } + +} diff --git a/apps/gallery/ajax/galleryOp.php b/apps/gallery/ajax/galleryOp.php index 25976fa9e3..1b3ad48f56 100644 --- a/apps/gallery/ajax/galleryOp.php +++ b/apps/gallery/ajax/galleryOp.php @@ -97,6 +97,8 @@ function handleGetGallery($path) { while ($r = $result->fetchRow()) { $album_name = $r['album_name']; $size=OC_Gallery_Album::getAlbumSize($r['album_id']); + // this is a fallback mechanism and seems expensive + if ($size == 0) $size = OC_Gallery_Album::getIntermediateGallerySize($r['album_path']); $a[] = array('name' => utf8_encode($album_name), 'numOfItems' => min($size, 10),'path'=>substr($r['album_path'], $pathLen)); } @@ -109,9 +111,51 @@ function handleGetGallery($path) { $p[] = utf8_encode($r['file_path']); } - OC_JSON::success(array('albums'=>$a, 'photos'=>$p)); + $r = OC_Gallery_Sharing::getEntryByAlbumId($album_details['album_id']); + $shared = false; + $recursive = false; + $token = ''; + if ($row = $r->fetchRow()) { + $shared = true; + $recursive = ($row['recursive'] == 1)? true : false; + $token = $row['token']; + } + + OC_JSON::success(array('albums'=>$a, 'photos'=>$p, 'shared' => $shared, 'recursive' => $recursive, 'token' => $token)); } +function handleShare($path, $share, $recursive) { + $recursive = $recursive == 'true' ? 1 : 0; + $owner = OC_User::getUser(); + $root = OC_Preferences::getValue(OC_User::getUser(),'gallery', 'root', '/'); + $path = utf8_decode(rtrim($root.$path,'/')); + if($path == '') $path = '/'; + $r = OC_Gallery_Album::find($owner, null, $path); + if ($row = $r->fetchRow()) { + $albumId = $row['album_id']; + } else { + OC_JSON::error(array('cause' => 'Couldn\'t find requested gallery')); + exit; + } + + if ($share == false) { + OC_Gallery_Sharing::remove($albumId); + OC_JSON::success(array('sharing' => false)); + } else { // share, yeah \o/ + $r = OC_Gallery_Sharing::getEntryByAlbumId($albumId); + if (($row = $r->fetchRow())) { // update entry + OC_Gallery_Sharing::updateSharingByToken($row['token'], $recursive); + OC_JSON::success(array('sharing' => true, 'token' => $row['token'], 'recursive' => $recursive == 1 ? true : false )); + } else { // and new sharing entry + $date = new DateTime(); + $token = md5($owner . $date->getTimestamp()); + OC_Gallery_Sharing::addShared($token, intval($albumId), $recursive); + OC_JSON::success(array('sharing' => true, 'token' => $token, 'recursive' => $recursive == 1 ? true : false )); + } + } +} + + if ($_GET['operation']) { switch($_GET['operation']) { case 'rename': @@ -134,6 +178,9 @@ if ($_GET['operation']) { case 'get_gallery': handleGetGallery($_GET['path']); break; + case 'share': + handleShare($_GET['path'], $_GET['share'] == 'true' ? true : false, $_GET['recursive']); + break; default: OC_JSON::error(array('cause' => 'Unknown operation')); } diff --git a/apps/gallery/ajax/sharing.php b/apps/gallery/ajax/sharing.php new file mode 100644 index 0000000000..fba85fa34e --- /dev/null +++ b/apps/gallery/ajax/sharing.php @@ -0,0 +1,115 @@ +. +* +*/ + +require_once('../../../lib/base.php'); + +if (!isset($_GET['token']) || !isset($_GET['operation'])) { + OC_JSON::error(array('cause' => 'Not enought arguments')); + exit; +} + +$operation = $_GET['operation']; +$token = $_GET['token']; + +if (!OC_Gallery_Sharing::isTokenValid($token)) { + OC_JSON::error(array('cause' => 'Given token is not valid')); + exit; +} + +function handleGetGallery($token, $path) { + $owner = OC_Gallery_Sharing::getTokenOwner($token); + $apath = OC_Gallery_Sharing::getPath($token); + + if ($path == false) + $root = $apath; + else + $root = rtrim($apath,'/').$path; + + $r = OC_Gallery_Album::find($owner, null, $root); + $albums = array(); + $photos = array(); + $albumId = -1; + if ($row = $r->fetchRow()) { + $albumId = $row['album_id']; + } + if ($albumId != -1) { + + if (OC_Gallery_Sharing::isRecursive($token)) { + $r = OC_Gallery_Album::find($owner, null, null, $root); + while ($row = $r->fetchRow()) + $albums[] = $row['album_name']; + } + + $r = OC_Gallery_Photo::find($albumId); + while ($row = $r->fetchRow()) + $photos[] = $row['file_path']; + } + + OC_JSON::success(array('albums' => $albums, 'photos' => $photos)); +} + +function handleGetThumbnail($token, $imgpath) { + $owner = OC_Gallery_Sharing::getTokenOwner($token); + $image = OC_Gallery_Photo::getThumbnail($imgpath, $owner); + if ($image) { + OC_Response::enableCaching(3600 * 24); // 24 hour + $image->show(); + } +} + +function handleGetAlbumThumbnail($token, $albumname) +{ + $owner = OC_Gallery_Sharing::getTokenOwner($token); + $file = OC_Config::getValue("datadirectory").'/'. $owner .'/gallery/'.$albumname.'.png'; + $image = new OC_Image($file); + if ($image->valid()) { + $image->centerCrop(); + $image->resize(200); + $image->fixOrientation(); + OC_Response::enableCaching(3600 * 24); // 24 hour + $image->show(); + } +} + +function handleGetPhoto($token, $photo) { + $owner = OC_Gallery_Sharing::getTokenOwner($token); + $file = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ).'/'.$owner.'/files'.urldecode($photo); + header('Content-Type: '.OC_Image::getMimeTypeForFile($file)); + OC_Response::sendFile($file); +} + +switch ($operation) { + case 'get_gallery': + handleGetGallery($token, isset($_GET['path'])? $_GET['path'] : false); + break; + case 'get_thumbnail': + handleGetThumbnail($token, urldecode($_GET['img'])); + break; + case 'get_album_thumbnail': + handleGetAlbumThumbnail($token, urldecode($_GET['albumname'])); + break; + case 'get_photo': + handleGetPhoto($token, urldecode($_GET['photo'])); + break; +} + diff --git a/apps/gallery/appinfo/app.php b/apps/gallery/appinfo/app.php index 1e5e27d408..b8e7c14fca 100644 --- a/apps/gallery/appinfo/app.php +++ b/apps/gallery/appinfo/app.php @@ -24,24 +24,25 @@ OC::$CLASSPATH['OC_Gallery_Album'] = 'apps/gallery/lib/album.php'; OC::$CLASSPATH['OC_Gallery_Photo'] = 'apps/gallery/lib/photo.php'; OC::$CLASSPATH['OC_Gallery_Scanner'] = 'apps/gallery/lib/scanner.php'; +OC::$CLASSPATH['OC_Gallery_Sharing'] = 'apps/gallery/lib/sharing.php'; OC::$CLASSPATH['OC_Gallery_Hooks_Handlers'] = 'apps/gallery/lib/hooks_handlers.php'; -$l = new OC_L10N('gallery'); +$l = OC_L10N::get('gallery'); OC_App::register(array( 'order' => 20, 'id' => 'gallery', - 'name' => 'Gallery')); + 'name' => 'Pictures')); OC_App::addNavigationEntry( array( 'id' => 'gallery_index', 'order' => 20, 'href' => OC_Helper::linkTo('gallery', 'index.php'), 'icon' => OC_Helper::imagePath('core', 'places/picture.svg'), - 'name' => $l->t('Gallery'))); + 'name' => $l->t('Pictures'))); - class OC_GallerySearchProvider implements OC_Search_Provider{ - static function search($query){ +class OC_GallerySearchProvider extends OC_Search_Provider{ + function search($query){ $stmt = OC_DB::prepare('SELECT * FROM *PREFIX*gallery_albums WHERE uid_owner = ? AND album_name LIKE ?'); $result = $stmt->execute(array(OC_User::getUser(),'%'.$query.'%')); $results=array(); diff --git a/apps/gallery/appinfo/database.xml b/apps/gallery/appinfo/database.xml index 62fdbee9cd..e3b13f7e93 100644 --- a/apps/gallery/appinfo/database.xml +++ b/apps/gallery/appinfo/database.xml @@ -67,4 +67,28 @@ + + *dbprefix*gallery_sharing + + + token + text + true + 64 + + + gallery_id + integer + 0 + true + 4 + + + recursive + integer + true + 1 + + +
    diff --git a/apps/gallery/appinfo/info.xml b/apps/gallery/appinfo/info.xml index 19c5dc8b25..7dc85374b0 100644 --- a/apps/gallery/appinfo/info.xml +++ b/apps/gallery/appinfo/info.xml @@ -1,11 +1,10 @@ gallery - Gallery - 0.4 + Pictures AGPL Bartek Przybylski 2 - Gallery application for ownCloud + Dedicated pictures application diff --git a/apps/gallery/appinfo/version b/apps/gallery/appinfo/version new file mode 100644 index 0000000000..e6adf3fc7b --- /dev/null +++ b/apps/gallery/appinfo/version @@ -0,0 +1 @@ +0.4 \ No newline at end of file diff --git a/apps/gallery/css/sharing.css b/apps/gallery/css/sharing.css new file mode 100644 index 0000000000..eaac82ebd6 --- /dev/null +++ b/apps/gallery/css/sharing.css @@ -0,0 +1,8 @@ +body { background-color: #eee; margin: 0; padding: 0;} +#gallery_list { height: 100%; width: 80%; background-color: white; margin: 0 auto; box-shadow: 0 0 8px #888; } +div.gallery_box { width: 200px; position:relative; text-align: center; border: 0; display: inline-block; margin: 5pt; vertical-align: top; padding: 5px 5px 5px 5px; position: relative; cursor: pointer; -webkit-transition: color 0.5s ease-in-out; -o-transition: color 0.5s ease-in-out; -moz-transition: color 0.5s ease-in-out;color: #BBB;} +div.gallery_box:hover { color: black; } +div.gallery_box h1 {font-size: 17px; font-weight: normal;} +div#breadcrumb { border: 0; width: 70%; margin: 0 auto; padding: 25px 0; font-family: Verdana; text-align: center;} +span.breadcrumbelement { margin: 10px; margin-right: 0; cursor: pointer;} +span.inside { background-image: url('../img/breadcrumb.png'); padding-left: 20px; background-position: left; background-repeat: no-repeat;} diff --git a/apps/gallery/css/styles.css b/apps/gallery/css/styles.css index 013cd1b262..fbf54e43db 100644 --- a/apps/gallery/css/styles.css +++ b/apps/gallery/css/styles.css @@ -16,3 +16,5 @@ div.gallery_control_overlay a { color:white; } #g-settings {position: absolute; left 13.5em; top: 0;} input[type=button] { -webkit-transition: opacity 0.5s ease-in-out; -moz-transition: opacity 0.5s ease-in-out; -o-transition: opacity 0.5s ease-in-out; opacity: 1} input[type=button]:disabled { opacity: 0.5 } +.ui-dialog tr {background-color: #eee;} +.ui-dialog input {width: 90%;} diff --git a/apps/gallery/img/breadcrumb.png b/apps/gallery/img/breadcrumb.png new file mode 100644 index 0000000000..a252a75155 Binary files /dev/null and b/apps/gallery/img/breadcrumb.png differ diff --git a/apps/gallery/index.php b/apps/gallery/index.php index 822a5b8e14..7de7c09414 100644 --- a/apps/gallery/index.php +++ b/apps/gallery/index.php @@ -4,7 +4,7 @@ * ownCloud - gallery application * * @author Bartek Przybylski -* @copyright 2012 Bartek Przybylski bart.p.pl@gmail.com +* @copyright 2012 Bartek Przybylski bartek@alefzero.eu * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -29,9 +29,6 @@ OC_App::setActiveNavigationEntry( 'gallery_index' ); if (!file_exists(OC_Config::getValue("datadirectory").'/'. OC_User::getUser() .'/gallery')) { mkdir(OC_Config::getValue("datadirectory").'/'. OC_User::getUser() .'/gallery'); - $f = fopen(OC_Config::getValue("datadirectory").'/'. OC_User::getUser() .'/gallery/.htaccess', 'w'); - fwrite($f, "allow from all"); - fclose($f); } if (!isset($_GET['view'])) { diff --git a/apps/gallery/js/album_cover.js b/apps/gallery/js/album_cover.js index 8cafce35f9..d44e7f83d1 100644 --- a/apps/gallery/js/album_cover.js +++ b/apps/gallery/js/album_cover.js @@ -30,17 +30,62 @@ function albumClick(title) { }); } +function constructSharingPath() { + return document.location.protocol + '//' + document.location.host + OC.linkTo('gallery', 'sharing.php') + '?token=' + Albums.token; +} + +function shareGallery() { + var existing_token = ''; + if (Albums.token) + existing_token = constructSharingPath(); + var form_fields = [{text: 'Share', name: 'share', type: 'checkbox', value: Albums.shared}, + {text: 'Share recursive', name: 'recursive', type: 'checkbox', value: Albums.recursive}, + {text: 'Shared gallery address', name: 'address', type: 'text', value: existing_token}]; + OC.dialogs.form(form_fields, t('gallery', 'Share gallery'), function(values){ + var p = ''; + for (var i in paths) p += paths[i]+'/'; + if (p == '') p = '/'; + alert(p); + $.getJSON(OC.filePath('gallery', 'ajax', 'galleryOp.php'), {operation: 'share', path: p, share: values[0].value, recursive: values[1].value}, function(r) { + if (r.status == 'success') { + Albums.shared = r.sharing; + if (Albums.shared) { + Albums.token = r.token; + Albums.recursive = r.recursive; + } else { + Albums.token = ''; + Albums.recursive = false; + } + var actual_addr = ''; + if (Albums.token) + actual_addr = constructSharingPath(); + $('input[name="address"]').val(actual_addr); + } else { + OC.dialogs.alert(t('gallery', 'Error: ') + r.cause, t('gallery', 'Internal error')); + } + }); + }); +} + function albumClickHandler(r) { Albums.photos = []; Albums.albums = []; if (r.status == 'success') { for (var i in r.albums) { var a = r.albums[i]; - Albums.add(a.name, a.numOfItems,a.path); + Albums.add(a.name, a.numOfItems, a.path, a.shared, a.recursive, a.token); } for (var i in r.photos) { - Albums.photos.push(r.photos[i]); + Albums.photos.push(r.photos[i]); } + Albums.shared = r.shared; + if (Albums.shared) { + Albums.recursive = r.recursive; + Albums.token = r.token; + } else { + Albums.recursive = false; + Albums.token = ''; + } var targetDiv = document.getElementById('gallery_list'); if (targetDiv) { $(targetDiv).html(''); @@ -54,7 +99,7 @@ function albumClickHandler(r) { OC.dialogs.alert(t('gallery', 'Error: no such layer `gallery_list`'), t('gallery', 'Internal error')); } } else { - OC.dialogs.alert(t('gallery', 'Error: ') + r.message, t('gallery', 'Internal error')); + OC.dialogs.alert(t('gallery', 'Error: ') + r.cause, t('gallery', 'Internal error')); } $('#g-album-loading').hide(); } @@ -68,42 +113,28 @@ function scanForAlbums(cleanup) { } function settings() { - $( '#g-dialog-settings' ).dialog({ - height: 180, - width: 350, - modal: false, - buttons: [ - { - text: t('gallery', 'Apply'), - click: function() { - var scanning_root = $('#g-scanning-root').val(); - var disp_order = $('#g-display-order option:selected').val(); + OC.dialogs.form([{text: t('gallery', 'Scanning root'), name: 'root', type:'text', value:gallery_scanning_root}, + {text: t('gallery', 'Default order'), name: 'order', type:'select', value:gallery_default_order, options:[ + {text:t('gallery', 'Ascending'), value:'ASC'}, {text: t('gallery', 'Descending'), value:'DESC'} ]}], + t('gallery', 'Settings'), + function(values) { + var scanning_root = values[0].value; + var disp_order = values[1].value; if (scanning_root == '') { - alert('Scanning root cannot be empty'); + OC.dialogs.alert(t('gallery', 'Scanning root cannot be empty'), t('gallery', 'Error')); return; } $.getJSON(OC.filePath('gallery','ajax','galleryOp.php'), {operation: 'store_settings', root: scanning_root, order: disp_order}, function(r) { if (r.status == 'success') { - if (r.rescan == 'yes') { - $('#g-dialog-settings').dialog('close'); - Albums.clear(document.getElementById('gallery_list')); - scanForAlbums(true); - return; - } + if (r.rescan == 'yes') { + Albums.clear(document.getElementById('gallery_list')); + scanForAlbums(true); + } + gallery_scanning_root = scanning_root; } else { - alert('Error: ' + r.cause); - return; + OC.dialogs.alert(t('gallery', 'Error: ') + r.cause, t('gallery', 'Error')); + return; } - $('#g-dialog-settings').dialog('close'); }); - } - }, - { - text: t('gallery', 'Cancel'), - click: function() { - $(this).dialog('close'); - } - } - ], - }); + }); } diff --git a/apps/gallery/js/albums.js b/apps/gallery/js/albums.js index d3326841cc..1fb38a5546 100644 --- a/apps/gallery/js/albums.js +++ b/apps/gallery/js/albums.js @@ -8,12 +8,15 @@ Albums={ // the album cover albums:new Array(), photos:new Array(), + shared: false, + recursive: false, + token: '', // add simply adds new album to internal structure // however albums names must be unique so other // album with the same name wont be insered, // and false will be returned // true on success - add: function(album_name, num,path) { + add: function(album_name, num, path) { if (Albums.albums[album_name] != undefined) return false; Albums.albums[album_name] = {name: album_name, numOfCovers: num, path:path}; return true; @@ -64,14 +67,14 @@ Albums={ $(".gallery_album_cover", local).css('background-repeat', 'no-repeat'); $(".gallery_album_cover", local).css('background-position', '0'); $(".gallery_album_cover", local).css('background-image','url("'+OC.filePath('gallery','ajax','galleryOp.php')+'?operation=get_covers&albumname='+escape(a.name)+'")'); - $(".gallery_album_cover", local).mousemove(function(e) { + $(".gallery_album_cover", local).mousemove(function(event) { var albumMetadata = Albums.find(this.title); if (albumMetadata == undefined) { return; } - var x = Math.floor((e.layerX - this.offsetLeft)/(this.offsetWidth/albumMetadata.numOfCovers)); + var x = Math.floor(event.offsetX/(this.offsetWidth/albumMetadata.numOfCovers)); x *= this.offsetWidth; - if (x < 0) x=0; + if (x < 0 || isNaN(x)) x=0; $(this).css('background-position', -x+'px 0'); }); $(element).append(local); diff --git a/apps/gallery/js/sharing.js b/apps/gallery/js/sharing.js new file mode 100644 index 0000000000..340d1b9b27 --- /dev/null +++ b/apps/gallery/js/sharing.js @@ -0,0 +1,57 @@ +$(document).ready(function() { + $.getJSON('ajax/sharing.php', {operation: 'get_gallery', token: TOKEN}, albumClickHandler); +}); + +var paths = []; +var counter = 0; + +function returnTo(num) { + while (num != counter) { + paths.pop(); + $('.breadcrumbelement:last').remove(); + counter--; + } + path = ''; + for (var e in paths) path += '/' + paths[e]; + $.getJSON('ajax/sharing.php', {operation: 'get_gallery', token: TOKEN, path: path}, function(r) { + albumClickHandler(r); + }); +} + +function albumClickHandler(r) { + var element = $('div#gallery_list'); + element.html(''); + var album_template = ''; + + for (var i in r.albums) { + var a = r.albums[i]; + var local = $(album_template.replace('IMGPATH', encodeURIComponent(a))); + local.attr('title', a); + $('h1', local).html(a); + element.append(local); + } + + $('div.gallery_box').each(function(i, element) { + $(element).click(function() { + paths.push($(this).attr('title')); + path = ''; + for (var e in paths) path += '/' + paths[e]; + $.getJSON('ajax/sharing.php', {operation: 'get_gallery', token: TOKEN, path: path}, function(r) { + var name = paths[paths.length-1]; + counter++; + var d = ''+name+''; + d = $(d).addClass('inside'); + $('#breadcrumb').append(d); + albumClickHandler(r); + }); + }); + }); + + var pat = ''; + for (var a in paths) pat += '/'+paths[a]; + var photo_template = ''; + for (var a in r.photos) { + var local = photo_template.replace('IMGPATH', encodeURIComponent(r.photos[a])).replace('*HREF*', 'ajax/sharing.php?token='+TOKEN+'&operation=get_photo&photo='+encodeURIComponent(r.photos[a])); + element.append(local); + } +} diff --git a/apps/gallery/lib/album.php b/apps/gallery/lib/album.php index e8698590e6..ef361a3791 100644 --- a/apps/gallery/lib/album.php +++ b/apps/gallery/lib/album.php @@ -79,7 +79,7 @@ class OC_Gallery_Album { $sql .= ' AND parent_path = ?'; $args[] = $parent; } - $order = OC_Preferences::getValue(OC_User::getUser(), 'gallery', 'order', 'ASC'); + $order = OC_Preferences::getValue($owner, 'gallery', 'order', 'ASC'); $sql .= ' ORDER BY album_name ' . $order; $stmt = OC_DB::prepare($sql); @@ -98,11 +98,19 @@ class OC_Gallery_Album { } public static function getAlbumSize($id){ - $sql = 'SELECT COUNT(*) as size FROM *PREFIX*gallery_photos WHERE album_id = ?'; - $stmt = OC_DB::prepare($sql); - $result=$stmt->execute(array($id))->fetchRow(); - return $result['size']; + $sql = 'SELECT COUNT(*) as size FROM *PREFIX*gallery_photos WHERE album_id = ?'; + $stmt = OC_DB::prepare($sql); + $result=$stmt->execute(array($id))->fetchRow(); + return $result['size']; } + + public static function getIntermediateGallerySize($path) { + $path .= '%'; + $sql = 'SELECT COUNT(*) as size FROM *PREFIX*gallery_photos photos, *PREFIX*gallery_albums albums WHERE photos.album_id = albums.album_id AND uid_owner = ? AND file_path LIKE ?'; + $stmt = OC_DB::prepare($sql); + $result = $stmt->execute(array(OC_User::getUser(), $path))->fetchRow(); + return $result['size']; + } } ?> diff --git a/apps/gallery/lib/photo.php b/apps/gallery/lib/photo.php index 5e6df8069b..3bb6f9129f 100644 --- a/apps/gallery/lib/photo.php +++ b/apps/gallery/lib/photo.php @@ -66,11 +66,15 @@ class OC_Gallery_Photo { $stmt->execute(array($newpath, $newAlbumId, $oldAlbumId, $oldpath)); } - public static function getThumbnail($image_name) { - $save_dir = OC_Config::getValue("datadirectory").'/'. OC_User::getUser() .'/gallery/'; + public static function getThumbnail($image_name, $owner = null) { + if (!$owner) $owner = OC_User::getUser(); + $save_dir = OC_Config::getValue("datadirectory").'/'. $owner .'/gallery/'; $save_dir .= dirname($image_name). '/'; $image_path = $image_name; $thumb_file = $save_dir . basename($image_name); + if (!is_dir($save_dir)) { + mkdir($save_dir, 0777, true); + } if (file_exists($thumb_file)) { $image = new OC_Image($thumb_file); } else { @@ -80,17 +84,15 @@ class OC_Gallery_Photo { } $image = new OC_Image($image_path); if ($image->valid()) { - $image->centerCrop(); - $image->resize(200); + $image->centerCrop(200); $image->fixOrientation(); - if (!is_dir($save_dir)) { - mkdir($save_dir, 0777, true); - } $image->save($thumb_file); } } if ($image->valid()) { return $image; + }else{ + $image->destroy(); } return null; } diff --git a/apps/gallery/lib/scanner.php b/apps/gallery/lib/scanner.php index 5791662522..0317f943e5 100644 --- a/apps/gallery/lib/scanner.php +++ b/apps/gallery/lib/scanner.php @@ -78,9 +78,11 @@ class OC_Gallery_Scanner { $image = OC_Gallery_Photo::getThumbnail($files[$i]); if ($image && $image->valid()) { imagecopyresampled($thumbnail, $image->resource(), $i*200, 0, 0, 0, 200, 200, 200, 200); + $image->destroy(); } } imagepng($thumbnail, OC_Config::getValue("datadirectory").'/'. OC_User::getUser() .'/gallery/' . $albumName.'.png'); + imagedestroy($thumbnail); } public static function createIntermediateAlbums() { @@ -95,6 +97,15 @@ class OC_Gallery_Scanner { foreach ($a as $e) { $p .= ($p == '/'?'':'/').$e; OC_Gallery_Album::create(OC_User::getUser(), $e, $p); + $arr = OC_FileCache::searchByMime('image','', OC_Filesystem::getRoot().$p); + $step = floor(count($arr)/10); + if ($step == 0) $step = 1; + $na = array(); + for ($j = 0; $j < count($arr); $j+=$step) { + $na[] = $p.$arr[$j]; + } + if (count($na)) + self::createThumbnails($e, $na); } } } @@ -131,4 +142,3 @@ class OC_Gallery_Scanner { } } -?> diff --git a/apps/gallery/lib/sharing.php b/apps/gallery/lib/sharing.php new file mode 100644 index 0000000000..60f108bd6c --- /dev/null +++ b/apps/gallery/lib/sharing.php @@ -0,0 +1,89 @@ +. +* +*/ + +class OC_Gallery_Sharing { + private static function getEntries($token) { + $sql = 'SELECT * FROM *PREFIX*gallery_sharing WHERE token = ?'; + $stmt = OC_DB::prepare($sql); + return $stmt->execute(array($token)); + } + + public static function isTokenValid($token) { + $r = self::getEntries($token); + $row = $r->fetchRow(); + return $row != null; + } + + public static function isRecursive($token) { + $r = self::getEntries($token); + if ($row = $r->fetchRow()) return $row['recursive'] == 1; + return false; + } + + public static function getTokenOwner($token) { + $r = self::getEntries($token); + if ($row = $r->fetchRow()) { + $galleryId = $row['gallery_id']; + $sql = 'SELECT * FROM *PREFIX*gallery_albums WHERE album_id = ?'; + $stmt = OC_DB::prepare($sql); + $r = $stmt->execute(array($galleryId)); + if ($row = $r->fetchRow()) + return $row['uid_owner']; + } + return false; + } + + public static function getPath($token) { + $r = self::getEntries($token); + if ($row = $r->fetchRow()) { + $galleryId = $row['gallery_id']; + $sql = 'SELECT * FROM *PREFIX*gallery_albums WHERE album_id = ?'; + $stmt = OC_DB::prepare($sql); + $r = $stmt->execute(array($galleryId)); + if ($row = $r->fetchRow()) + return $row['album_path']; + } + } + + public static function updateSharingByToken($token, $recursive) { + $stmt = OC_DB::prepare('UPDATE *PREFIX*gallery_sharing SET recursive = ? WHERE token = ?'); + $stmt->execute(array($recursive, $token)); + } + + public static function getEntryByAlbumId($album_id) { + $stmt = OC_DB::prepare('SELECT * FROM *PREFIX*gallery_sharing WHERE gallery_id = ?'); + return $stmt->execute(array($album_id)); + } + + public static function addShared($token, $albumId, $recursive) { + $sql = 'INSERT INTO *PREFIX*gallery_sharing (token, gallery_id, recursive) VALUES (?, ?, ?)'; + $stmt = OC_DB::prepare($sql); + $stmt->execute(array($token, $albumId, $recursive)); + } + + public static function remove($albumId) { + $stmt = OC_DB::prepare('DELETE FROM *PREFIX*gallery_sharing WHERE gallery_id = ?'); + $stmt->execute(array($albumId)); + } +} + diff --git a/apps/gallery/sharing.php b/apps/gallery/sharing.php new file mode 100644 index 0000000000..d7430becf4 --- /dev/null +++ b/apps/gallery/sharing.php @@ -0,0 +1,47 @@ +. +* +*/ + +if (!isset($_GET['token']) || empty($_GET['token'])) { + exit; +} + +require_once('../../lib/base.php'); + +OC_Util::checkAppEnabled('gallery'); + +?> + + + + + + + + + + + + + diff --git a/apps/gallery/templates/index.php b/apps/gallery/templates/index.php index 54fbcfd015..cf654b68c0 100644 --- a/apps/gallery/templates/index.php +++ b/apps/gallery/templates/index.php @@ -7,18 +7,19 @@ OC_Util::addStyle('files', 'files'); OC_Util::addScript('files_imageviewer', 'jquery.mousewheel-3.0.4.pack'); OC_Util::addScript('files_imageviewer', 'jquery.fancybox-1.3.4.pack'); OC_Util::addStyle( 'files_imageviewer', 'jquery.fancybox-1.3.4' ); -$l = new OC_L10N('gallery'); +$l = OC_L10N::get('gallery'); ?> - +
    - + +
    -
    +
    @@ -28,40 +29,3 @@ $l = new OC_L10N('gallery');
    - - - - - - - diff --git a/apps/gallery/templates/view_album.php b/apps/gallery/templates/view_album.php index 6b513a672d..f938e48795 100644 --- a/apps/gallery/templates/view_album.php +++ b/apps/gallery/templates/view_album.php @@ -5,7 +5,7 @@ OC_Util::addScript('gallery', 'album_cover'); OC_Util::addScript('files_imageviewer', 'jquery.mousewheel-3.0.4.pack'); OC_Util::addScript('files_imageviewer', 'jquery.fancybox-1.3.4.pack'); OC_Util::addStyle( 'files_imageviewer', 'jquery.fancybox-1.3.4' ); -$l = new OC_L10N('gallery'); +$l = OC_L10N::get('gallery'); ?> '.PHP_EOL; + $response=''.PHP_EOL; echo $response; }else{ if($type){ diff --git a/lib/filecache.php b/lib/filecache.php index 280a9929db..24984c2ccf 100644 --- a/lib/filecache.php +++ b/lib/filecache.php @@ -59,8 +59,8 @@ class OC_FileCache{ $root=''; } $path=$root.$path; - $query=OC_DB::prepare('SELECT ctime,mtime,mimetype,size,encrypted,versioned,writable FROM *PREFIX*fscache WHERE path=?'); - $result=$query->execute(array($path))->fetchRow(); + $query=OC_DB::prepare('SELECT ctime,mtime,mimetype,size,encrypted,versioned,writable FROM *PREFIX*fscache WHERE path_hash=?'); + $result=$query->execute(array(md5($path)))->fetchRow(); if(is_array($result)){ return $result; }else{ @@ -111,8 +111,8 @@ class OC_FileCache{ } $mimePart=dirname($data['mimetype']); $user=OC_User::getUser(); - $query=OC_DB::prepare('INSERT INTO *PREFIX*fscache(parent, name, path, size, mtime, ctime, mimetype, mimepart,user,writable,encrypted,versioned) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)'); - $result=$query->execute(array($parent,basename($path),$path,$data['size'],$data['mtime'],$data['ctime'],$data['mimetype'],$mimePart,$user,$data['writable'],$data['encrypted'],$data['versioned'])); + $query=OC_DB::prepare('INSERT INTO *PREFIX*fscache(parent, name, path, path_hash, size, mtime, ctime, mimetype, mimepart,user,writable,encrypted,versioned) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)'); + $result=$query->execute(array($parent,basename($path),$path,md5($path),$data['size'],$data['mtime'],$data['ctime'],$data['mimetype'],$mimePart,$user,$data['writable'],$data['encrypted'],$data['versioned'])); if(OC_DB::isError($result)){ OC_Log::write('files','error while writing file('.$path.') to cache',OC_Log::ERROR); } @@ -162,8 +162,8 @@ class OC_FileCache{ $oldPath=$root.$oldPath; $newPath=$root.$newPath; $newParent=self::getParentId($newPath); - $query=OC_DB::prepare('UPDATE *PREFIX*fscache SET parent=? ,name=?, path=? WHERE path=?'); - $query->execute(array($newParent,basename($newPath),$newPath,$oldPath)); + $query=OC_DB::prepare('UPDATE *PREFIX*fscache SET parent=? ,name=?, path=?, path_hash=? WHERE path_hash=?'); + $query->execute(array($newParent,basename($newPath),$newPath,md5($newPath),md5($oldPath))); } /** @@ -240,7 +240,7 @@ class OC_FileCache{ * - encrypted * - versioned */ - public static function getFolderContent($path,$root=''){ + public static function getFolderContent($path,$root='',$mimetype_filter=''){ if(self::isUpdated($path,$root)){ self::updateFolder($path,$root); } @@ -252,8 +252,8 @@ class OC_FileCache{ } $path=$root.$path; $parent=self::getFileId($path); - $query=OC_DB::prepare('SELECT name,ctime,mtime,mimetype,size,encrypted,versioned,writable FROM *PREFIX*fscache WHERE parent=?'); - $result=$query->execute(array($parent))->fetchAll(); + $query=OC_DB::prepare('SELECT name,ctime,mtime,mimetype,size,encrypted,versioned,writable FROM *PREFIX*fscache WHERE parent=? AND (mimetype LIKE ? OR mimetype = ?)'); + $result=$query->execute(array($parent, $mimetype_filter.'%', 'httpd/unix-directory'))->fetchAll(); if(is_array($result)){ return $result; }else{ @@ -281,16 +281,17 @@ class OC_FileCache{ /** * get the file id as used in the cache + * unlike the public getId, full paths are used here (/usename/files/foo instead of /foo) * @param string $path * @return int */ private static function getFileId($path){ - $query=OC_DB::prepare('SELECT id FROM *PREFIX*fscache WHERE path=?'); + $query=OC_DB::prepare('SELECT id FROM *PREFIX*fscache WHERE path_hash=?'); if(OC_DB::isError($query)){ OC_Log::write('files','error while getting file id of '.$path,OC_Log::ERROR); return -1; } - $result=$query->execute(array($path)); + $result=$query->execute(array(md5($path))); if(OC_DB::isError($result)){ OC_Log::write('files','error while getting file id of '.$path,OC_Log::ERROR); return -1; @@ -303,6 +304,39 @@ class OC_FileCache{ return -1; } } + + /** + * get the file id as used in the cache + * @param string path + * @param string root (optional) + * @return int + */ + public static function getId($path,$root=''){ + if(!$root){ + $root=OC_Filesystem::getRoot(); + } + if($root=='/'){ + $root=''; + } + $path=$root.$path; + return self::getFileId($path); + } + + /** + * get the file path from the id, relative to the home folder of the user + * @param int id + * @param string user (optional) + * @return string + */ + public static function getPath($id,$user=''){ + if(!$user){ + $user=OC_User::getUser(); + } + $query=OC_DB::prepare('SELECT path FROM *PREFIX*fscache WHERE id=? AND user=?'); + $result=$query->execute(array($id,$user)); + $row=$result->fetchRow(); + return $row['path']; + } /** * get the file id of the parent folder, taking into account '/' has no parent @@ -367,8 +401,8 @@ class OC_FileCache{ } } $path=$root.$path; - $query=OC_DB::prepare('SELECT ctime,mtime,mimetype,size,encrypted,versioned,writable FROM *PREFIX*fscache WHERE path=?'); - $result=$query->execute(array($path))->fetchRow(); + $query=OC_DB::prepare('SELECT ctime,mtime,mimetype,size,encrypted,versioned,writable FROM *PREFIX*fscache WHERE path_hash=?'); + $result=$query->execute(array(md5($path)))->fetchRow(); if(is_array($result)){ if(isset(self::$savedData[$path])){ $result=array_merge($result,self::$savedData[$path]); @@ -389,8 +423,8 @@ class OC_FileCache{ } } $path=$root.$path; - $query=OC_DB::prepare('SELECT size FROM *PREFIX*fscache WHERE path=?'); - $result=$query->execute(array($path)); + $query=OC_DB::prepare('SELECT size FROM *PREFIX*fscache WHERE path_hash=?'); + $result=$query->execute(array(md5($path))); if($row=$result->fetchRow()){ return $row['size']; }else{//file not in cache @@ -469,6 +503,10 @@ class OC_FileCache{ * @param string root (optionak) */ public static function scan($path,$eventSource=false,&$count=0,$root=''){ + if($eventSource){ + $eventSource->send('scanning',array('file'=>$path,'count'=>$count)); + } + $lastSend=$count; if(!$root){ $view=OC_Filesystem::getView(); }else{ @@ -482,13 +520,14 @@ class OC_FileCache{ if($filename != '.' and $filename != '..'){ $file=$path.'/'.$filename; if($view->is_dir($file.'/')){ - if($eventSource){ - $eventSource->send('scanning',array('file'=>$file,'count'=>$count)); - } self::scan($file,$eventSource,$count,$root); }else{ $totalSize+=self::scanFile($file,$root); $count++; + if($count>$lastSend+25 and $eventSource){ + $lastSend=$count; + $eventSource->send('scanning',array('file'=>$path,'count'=>$count)); + } } } } @@ -579,8 +618,8 @@ class OC_FileCache{ $mtime=$view->filemtime($path); $isDir=$view->is_dir($path); $path=$root.$path; - $query=OC_DB::prepare('SELECT mtime FROM *PREFIX*fscache WHERE path=?'); - $result=$query->execute(array($path)); + $query=OC_DB::prepare('SELECT mtime FROM *PREFIX*fscache WHERE path_hash=?'); + $result=$query->execute(array(md5($path))); if($row=$result->fetchRow()){ $cachedMTime=$row['mtime']; return ($mtime>$cachedMTime); @@ -637,6 +676,14 @@ class OC_FileCache{ self::fileSystemWatcherWrite(array('path'=>$path),$root); } } + + /** + * clean old pre-path_hash entries + */ + public static function clean(){ + $query=OC_DB::prepare('DELETE FROM *PREFIX*fscache WHERE LENGTH(path_hash)<30'); + $query->execute(); + } } //watch for changes and try to keep the cache up to date diff --git a/lib/files.php b/lib/files.php index 1f8331afb2..a7b8314957 100644 --- a/lib/files.php +++ b/lib/files.php @@ -32,11 +32,11 @@ class OC_Files { * get the content of a directory * @param dir $directory */ - public static function getDirectoryContent($directory){ + public static function getDirectoryContent($directory, $mimetype_filter = ''){ if(strpos($directory,OC::$CONFIG_DATADIRECTORY)===0){ $directory=substr($directory,strlen(OC::$CONFIG_DATADIRECTORY)); } - $files=OC_FileCache::getFolderContent($directory); + $files=OC_FileCache::getFolderContent($directory, '', $mimetype_filter); foreach($files as &$file){ $file['directory']=$directory; $file['type']=($file['mimetype']=='httpd/unix-directory')?'dir':'file'; @@ -59,9 +59,12 @@ class OC_Files { } if(is_array($files)){ + self::validateZipDownload($dir,$files); + $executionTime = intval(ini_get('max_execution_time')); + set_time_limit(0); $zip = new ZipArchive(); - $filename = get_temp_dir()."/ownCloud.zip"; - if ($zip->open($filename, ZIPARCHIVE::CREATE)!==TRUE) { + $filename = OC_Helper::tmpFile('.zip'); + if ($zip->open($filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE)!==TRUE) { exit("cannot open <$filename>\n"); } foreach($files as $file){ @@ -75,15 +78,20 @@ class OC_Files { } } $zip->close(); + set_time_limit($executionTime); }elseif(OC_Filesystem::is_dir($dir.'/'.$files)){ + self::validateZipDownload($dir,$files); + $executionTime = intval(ini_get('max_execution_time')); + set_time_limit(0); $zip = new ZipArchive(); - $filename = get_temp_dir()."/ownCloud.zip"; - if ($zip->open($filename, ZIPARCHIVE::CREATE)!==TRUE) { + $filename = OC_Helper::tmpFile('.zip'); + if ($zip->open($filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE)!==TRUE) { exit("cannot open <$filename>\n"); } $file=$dir.'/'.$files; self::zipAddDir($file,$zip); $zip->close(); + set_time_limit($executionTime); }else{ $zip=false; $filename=$dir.'/'.$files; @@ -96,15 +104,15 @@ class OC_Files { header('Content-Type: application/zip'); header('Content-Length: ' . filesize($filename)); }else{ - header('Content-Type: ' . OC_Filesystem::getMimeType($filename)); - header('Content-Length: ' . OC_Filesystem::filesize($filename)); + $fileData=OC_FileCache::get($filename); + header('Content-Type: ' . $fileData['mimetype']); + header('Content-Length: ' . $fileData['size']); } }elseif($zip or !OC_Filesystem::file_exists($filename)){ header("HTTP/1.0 404 Not Found"); $tmpl = new OC_Template( '', '404', 'guest' ); $tmpl->assign('file',$filename); $tmpl->printPage(); -// die('404 Not Found'); }else{ header("HTTP/1.0 403 Forbidden"); die('403 Forbidden'); @@ -209,6 +217,55 @@ class OC_Files { } } + /** + * checks if the selected files are within the size constraint. If not, outputs an error page. + * + * @param dir $dir + * @param files $files + */ + static function validateZipDownload($dir, $files) { + if(!OC_Config::getValue('allowZipDownload', true)) { + $l = OC_L10N::get('files'); + header("HTTP/1.0 409 Conflict"); + $tmpl = new OC_Template( '', 'error', 'user' ); + $errors = array( + array( + 'error' => $l->t('ZIP download is turned off.'), + 'hint' => $l->t('Files need to be downloaded one by one.') . '
    ' . $l->t('Back to Files') . '', + ) + ); + $tmpl->assign('errors', $errors); + $tmpl->printPage(); + exit; + } + + $zipLimit = OC_Config::getValue('maxZipInputSize', OC_Helper::computerFileSize('800 MB')); + if($zipLimit > 0) { + $totalsize = 0; + if(is_array($files)){ + foreach($files as $file){ + $totalsize += OC_Filesystem::filesize($dir.'/'.$file); + } + }else{ + $totalsize += OC_Filesystem::filesize($dir.'/'.$files); + } + if($totalsize > $zipLimit) { + $l = OC_L10N::get('files'); + header("HTTP/1.0 409 Conflict"); + $tmpl = new OC_Template( '', 'error', 'user' ); + $errors = array( + array( + 'error' => $l->t('Selected files too large to generate zip file.'), + 'hint' => 'Download the files in smaller chunks, seperately or kindly ask your administrator.
    ' . $l->t('Back to Files') . '', + ) + ); + $tmpl->assign('errors', $errors); + $tmpl->printPage(); + exit; + } + } + } + /** * try to detect the mime type of a file * @@ -256,21 +313,58 @@ class OC_Files { return false; } } - + /** * set the maximum upload size limit for apache hosts using .htaccess * @param int size filesisze in bytes + * @return false on failure, size on success */ static function setUploadLimit($size){ - $size=OC_Helper::humanFileSize($size); - $size=substr($size,0,-1);//strip the B - $size=str_replace(' ','',$size); //remove the space between the size and the postfix - $content = "ErrorDocument 404 /".OC::$WEBROOT."/core/templates/404.php\n";//custom 404 error page - $content.= "php_value upload_max_filesize $size\n";//upload limit - $content.= "php_value post_max_size $size\n"; - $content.= "SetEnv htaccessWorking true\n"; - $content.= "Options -Indexes\n"; - @file_put_contents(OC::$SERVERROOT.'/.htaccess', $content); //supress errors in case we don't have permissions for it + //don't allow user to break his config -- upper boundary + if($size > PHP_INT_MAX) { + //max size is always 1 byte lower than computerFileSize returns + if($size > PHP_INT_MAX+1) + return false; + $size -=1; + } else { + $size=OC_Helper::humanFileSize($size); + $size=substr($size,0,-1);//strip the B + $size=str_replace(' ','',$size); //remove the space between the size and the postfix + } + + //don't allow user to break his config -- broken or malicious size input + if(intval($size) == 0) { + return false; + } + + $htaccess = @file_get_contents(OC::$SERVERROOT.'/.htaccess'); //supress errors in case we don't have permissions for + if(!$htaccess) { + return false; + } + + $phpValueKeys = array( + 'upload_max_filesize', + 'post_max_size' + ); + + foreach($phpValueKeys as $key) { + $pattern = '/php_value '.$key.' (\S)*/'; + $setting = 'php_value '.$key.' '.$size; + $hasReplaced = 0; + $content = preg_replace($pattern, $setting, $htaccess, 1, $hasReplaced); + if($content !== NULL) { + $htaccess = $content; + } + if($hasReplaced == 0) { + $htaccess .= "\n" . $setting; + } + } + + //supress errors in case we don't have permissions for it + if(@file_put_contents(OC::$SERVERROOT.'/.htaccess', $htaccess)) { + return OC_Helper::computerFileSize($size); + } + return false; } /** diff --git a/lib/filestorage/common.php b/lib/filestorage/common.php index f632474df0..f0bfc064cb 100644 --- a/lib/filestorage/common.php +++ b/lib/filestorage/common.php @@ -100,11 +100,11 @@ abstract class OC_Filestorage_Common extends OC_Filestorage { } $head=fread($source,8192);//8kb should suffice to determine a mimetype if($pos=strrpos($path,'.')){ - $extention=substr($path,$pos); + $extension=substr($path,$pos); }else{ - $extention=''; + $extension=''; } - $tmpFile=OC_Helper::tmpFile($extention); + $tmpFile=OC_Helper::tmpFile($extension); file_put_contents($tmpFile,$head); $mime=OC_Helper::getMimeType($tmpFile); unlink($tmpFile); @@ -129,11 +129,11 @@ abstract class OC_Filestorage_Common extends OC_Filestorage { return false; } if($pos=strrpos($path,'.')){ - $extention=substr($path,$pos); + $extension=substr($path,$pos); }else{ - $extention=''; + $extension=''; } - $tmpFile=OC_Helper::tmpFile($extention); + $tmpFile=OC_Helper::tmpFile($extension); $target=fopen($tmpFile,'w'); $count=OC_Helper::streamCopy($source,$target); return $tmpFile; diff --git a/lib/filestorage/local.php b/lib/filestorage/local.php index 688501aee9..bd757f52ce 100644 --- a/lib/filestorage/local.php +++ b/lib/filestorage/local.php @@ -86,6 +86,10 @@ class OC_Filestorage_Local extends OC_Filestorage{ return $this->delTree($path); } public function rename($path1,$path2){ + if (!$this->is_writable($path1)) { + OC_Log::write('core','unable to rename, file is not writable : '.$path1,OC_Log::ERROR); + return false; + } if(! $this->file_exists($path1)){ OC_Log::write('core','unable to rename, file does not exists : '.$path1,OC_Log::ERROR); return false; diff --git a/lib/filesystem.php b/lib/filesystem.php index 12905d189f..dc678fba74 100644 --- a/lib/filesystem.php +++ b/lib/filesystem.php @@ -248,6 +248,14 @@ class OC_Filesystem{ return self::$defaultInstance->getRoot(); } + /** + * clear all mounts and storage backends + */ + public static function clearMounts(){ + self::$mounts=array(); + self::$storages=array(); + } + /** * mount an OC_Filestorage in our virtual filesystem * @param OC_Filestorage storage @@ -298,6 +306,19 @@ class OC_Filesystem{ } return true; } + + /** + * checks if a file is blacklsited for storage in the filesystem + * @param array $data from hook + */ + static public function isBlacklisted($data){ + $blacklist = array('.htaccess'); + $filename = strtolower(basename($data['path'])); + if(in_array($filename,$blacklist)){ + $data['run'] = false; + } + } + /** * following functions are equivilent to their php buildin equivilents for arguments/return values. */ diff --git a/lib/filesystemview.php b/lib/filesystemview.php index 89e0385fe9..95873bd87c 100644 --- a/lib/filesystemview.php +++ b/lib/filesystemview.php @@ -23,11 +23,11 @@ class OC_FilesystemView { private $fakeRoot=''; - + public function __construct($root){ $this->fakeRoot=$root; } - + public function getAbsolutePath($path){ if(!$path){ $path='/'; @@ -137,13 +137,16 @@ class OC_FilesystemView { } public function readfile($path){ $handle=$this->fopen($path,'r'); - $chunkSize = 1024*1024;// 1 MB chunks - while (!feof($handle)) { - echo fread($handle, $chunkSize); - @ob_flush(); - flush(); + if ($handle) { + $chunkSize = 1024*1024;// 1 MB chunks + while (!feof($handle)) { + echo fread($handle, $chunkSize); + @ob_flush(); + flush(); + } + return $this->filesize($path); } - return $this->filesize($path); + return false; } public function is_readable($path){ return $this->basicOperation('is_readable',$path); @@ -189,7 +192,7 @@ class OC_FilesystemView { return $this->basicOperation('unlink',$path,array('delete')); } public function rename($path1,$path2){ - if(OC_FileProxy::runPreProxies('rename',$path1,$path2) and $this->is_writable($path1) and OC_Filesystem::isValidPath($path2)){ + if(OC_FileProxy::runPreProxies('rename',$path1,$path2) and OC_Filesystem::isValidPath($path2)){ $run=true; OC_Hook::emit( OC_Filesystem::CLASSNAME, OC_Filesystem::signal_rename, array( OC_Filesystem::signal_param_oldpath => $path1 , OC_Filesystem::signal_param_newpath=>$path2, OC_Filesystem::signal_param_run => &$run)); if($run){ @@ -280,9 +283,14 @@ class OC_FilesystemView { if(OC_Filesystem::isValidPath($path)){ $source=$this->fopen($path,'r'); if($source){ - $extention=substr($path,strrpos($path,'.')); - $tmpFile=OC_Helper::tmpFile($extention); - return file_put_contents($tmpFile,$source); + $extension=''; + $extOffset=strpos($path,'.'); + if($extOffset !== false) { + $extension=substr($path,strrpos($path,'.')); + } + $tmpFile=OC_Helper::tmpFile($extension); + file_put_contents($tmpFile,$source); + return $tmpFile; } } } diff --git a/lib/group.php b/lib/group.php index fbff41e30e..4ae9302f78 100644 --- a/lib/group.php +++ b/lib/group.php @@ -34,57 +34,25 @@ * post_removeFromGroup(uid, gid) */ class OC_Group { - // The backend used for user management - private static $_backend; - - // Backends available (except database) - private static $_backends = array(); - - /** - * @brief registers backend - * @param $name name of the backend - * @returns true/false - * - * Makes a list of backends that can be used by other modules - */ - public static function registerBackend( $name ){ - self::$_backends[] = $name; - return true; - } - - /** - * @brief gets available backends - * @returns array of backends - * - * Returns the names of all backends. - */ - public static function getBackends(){ - return self::$_backends; - } + // The backend used for group management + private static $_usedBackends = array(); /** * @brief set the group backend * @param string $backend The backend to use for user managment * @returns true/false */ - public static function setBackend( $backend = 'database' ){ - // You'll never know what happens - if( null === $backend OR !is_string( $backend )){ - $backend = 'database'; + public static function useBackend( $backend ){ + if($backend instanceof OC_Group_Backend){ + self::$_usedBackends[]=$backend; } + } - // Load backend - switch( $backend ){ - case 'database': - case 'mysql': - case 'sqlite': - self::$_backend = new OC_Group_Database(); - break; - default: - $className = 'OC_GROUP_' . strToUpper($backend); - self::$_backend = new $className(); - break; - } + /** + * remove all used backends + */ + public static function clearBackends(){ + self::$_usedBackends=array(); } /** @@ -115,11 +83,18 @@ class OC_Group { $run = true; OC_Hook::emit( "OC_Group", "pre_createGroup", array( "run" => &$run, "gid" => $gid )); - if( $run && self::$_backend->createGroup( $gid )){ - OC_Hook::emit( "OC_Group", "post_createGroup", array( "gid" => $gid )); - return true; - } - else{ + if($run){ + //create the user in the first backend that supports creating users + foreach(self::$_usedBackends as $backend){ + if(!$backend->implementsActions(OC_GROUP_BACKEND_CREATE_GROUP)) + continue; + + $backend->createGroup($gid); + OC_Hook::emit( "OC_User", "post_createGroup", array( "gid" => $gid )); + + return true; + } + }else{ return false; } } @@ -140,11 +115,18 @@ class OC_Group { $run = true; OC_Hook::emit( "OC_Group", "pre_deleteGroup", array( "run" => &$run, "gid" => $gid )); - if( $run && self::$_backend->deleteGroup( $gid )){ - OC_Hook::emit( "OC_Group", "post_deleteGroup", array( "gid" => $gid )); - return true; - } - else{ + if($run){ + //delete the group from all backends + foreach(self::$_usedBackends as $backend){ + if(!$backend->implementsActions(OC_GROUP_BACKEND_DELETE_GROUP)) + continue; + + $backend->deleteGroup($gid); + OC_Hook::emit( "OC_User", "post_deleteGroup", array( "gid" => $gid )); + + return true; + } + }else{ return false; } } @@ -158,7 +140,15 @@ class OC_Group { * Checks whether the user is member of a group or not. */ public static function inGroup( $uid, $gid ){ - return self::$_backend->inGroup($uid, $gid); + foreach(self::$_usedBackends as $backend){ + if(!$backend->implementsActions(OC_GROUP_BACKEND_IN_GROUP)) + continue; + + if($backend->inGroup($uid,$gid)){ + return true; + } + } + return false; } /** @@ -170,10 +160,6 @@ class OC_Group { * Adds a user to a group. */ public static function addToGroup( $uid, $gid ){ - // Does the user exist? - if( !OC_User::userExists($uid)){ - return false; - } // Does the group exist? if( !OC_Group::groupExists($gid)){ return false; @@ -183,11 +169,19 @@ class OC_Group { $run = true; OC_Hook::emit( "OC_Group", "pre_addToGroup", array( "run" => &$run, "uid" => $uid, "gid" => $gid )); - if( $run && self::$_backend->addToGroup( $uid, $gid )){ - OC_Hook::emit( "OC_Group", "post_addToGroup", array( "uid" => $uid, "gid" => $gid )); - return true; - } - else{ + if($run){ + $succes=false; + + //add the user to the all backends that have the group + foreach(self::$_usedBackends as $backend){ + if(!$backend->implementsActions(OC_GROUP_BACKEND_ADD_TO_GROUP)) + continue; + + $succes|=$backend->addToGroup($uid, $gid); + OC_Hook::emit( "OC_User", "post_addToGroup", array( "uid" => $uid, "gid" => $gid )); + } + return $succes; + }else{ return false; } } @@ -204,11 +198,17 @@ class OC_Group { $run = true; OC_Hook::emit( "OC_Group", "pre_removeFromGroup", array( "run" => &$run, "uid" => $uid, "gid" => $gid )); - if( $run && self::$_backend->removeFromGroup( $uid, $gid )){ - OC_Hook::emit( "OC_Group", "post_removeFromGroup", array( "uid" => $uid, "gid" => $gid )); + if($run){ + //remove the user from the all backends that have the group + foreach(self::$_usedBackends as $backend){ + if(!$backend->implementsActions(OC_GROUP_BACKEND_REMOVE_FROM_GOUP)) + continue; + + $backend->removeFromGroup($uid, $gid); + OC_Hook::emit( "OC_User", "post_removeFromGroup", array( "uid" => $uid, "gid" => $gid )); + } return true; - } - else{ + }else{ return false; } } @@ -222,7 +222,14 @@ class OC_Group { * if the user exists at all. */ public static function getUserGroups( $uid ){ - return self::$_backend->getUserGroups($uid); + $groups=array(); + foreach(self::$_usedBackends as $backend){ + if(!$backend->implementsActions(OC_GROUP_BACKEND_GET_USER_GROUPS)) + continue; + + $groups=array_merge($backend->getUserGroups($uid),$groups); + } + return $groups; } /** @@ -232,7 +239,14 @@ class OC_Group { * Returns a list with all groups */ public static function getGroups(){ - return self::$_backend->getGroups(); + $groups=array(); + foreach(self::$_usedBackends as $backend){ + if(!$backend->implementsActions(OC_GROUP_BACKEND_GET_GROUPS)) + continue; + + $groups=array_merge($backend->getGroups(),$groups); + } + return $groups; } /** @@ -249,6 +263,13 @@ class OC_Group { * @returns array with user ids */ public static function usersInGroup($gid){ - return self::$_backend->usersInGroup($gid); + $users=array(); + foreach(self::$_usedBackends as $backend){ + if(!$backend->implementsActions(OC_GROUP_BACKEND_GET_USERS)) + continue; + + $users=array_merge($backend->usersInGroup($gid),$users); + } + return $users; } } diff --git a/lib/group/backend.php b/lib/group/backend.php index 43f94e7ea1..b3fc06ac9a 100644 --- a/lib/group/backend.php +++ b/lib/group/backend.php @@ -21,82 +21,65 @@ * */ +/** + * error code for functions not provided by the group backend + */ +define('OC_GROUP_BACKEND_NOT_IMPLEMENTED', -501); +/** + * actions that user backends can define + */ +define('OC_GROUP_BACKEND_CREATE_GROUP', 0x00000001); +define('OC_GROUP_BACKEND_DELETE_GROUP', 0x00000010); +define('OC_GROUP_BACKEND_IN_GROUP', 0x00000100); +define('OC_GROUP_BACKEND_ADD_TO_GROUP', 0x00001000); +define('OC_GROUP_BACKEND_REMOVE_FROM_GOUP', 0x00010000); +define('OC_GROUP_BACKEND_GET_USER_GROUPS', 0x00100000); +define('OC_GROUP_BACKEND_GET_USERS', 0x01000000); +define('OC_GROUP_BACKEND_GET_GROUPS', 0x10000000); /** * Abstract base class for user management */ abstract class OC_Group_Backend { - /** - * @brief Try to create a new group - * @param $gid The name of the group to create - * @returns true/false - * - * Trys to create a new group. If the group name already exists, false will - * be returned. - */ - public static function createGroup($gid){} - - /** - * @brief delete a group - * @param $gid gid of the group to delete - * @returns true/false - * - * Deletes a group and removes it from the group_user-table - */ - public static function removeGroup($gid){} - - /** - * @brief is user in group? - * @param $uid uid of the user - * @param $gid gid of the group - * @returns true/false - * - * Checks whether the user is member of a group or not. - */ - public static function inGroup($uid, $gid){} - - /** - * @brief Add a user to a group - * @param $uid Name of the user to add to group - * @param $gid Name of the group in which add the user - * @returns true/false - * - * Adds a user to a group. - */ - public static function addToGroup($uid, $gid){} - - /** - * @brief Removes a user from a group - * @param $uid Name of the user to remove from group - * @param $gid Name of the group from which remove the user - * @returns true/false - * - * removes the user from a group. - */ - public static function removeFromGroup($uid,$gid){} - - /** - * @brief Get all groups a user belongs to - * @param $uid Name of the user - * @returns array with group names - * - * This function fetches all groups a user belongs to. It does not check - * if the user exists at all. - */ - public static function getUserGroups($uid){} - - /** - * @brief get a list of all groups - * @returns array with group names - * - * Returns a list with all groups - */ - public static function getGroups(){} + protected $possibleActions = array( + OC_GROUP_BACKEND_CREATE_GROUP => 'createGroup', + OC_GROUP_BACKEND_DELETE_GROUP => 'deleteGroup', + OC_GROUP_BACKEND_IN_GROUP => 'inGroup', + OC_GROUP_BACKEND_ADD_TO_GROUP => 'addToGroup', + OC_GROUP_BACKEND_REMOVE_FROM_GOUP => 'removeFromGroup', + OC_GROUP_BACKEND_GET_USER_GROUPS => 'getUserGroups', + OC_GROUP_BACKEND_GET_USERS => 'usersInGroup', + OC_GROUP_BACKEND_GET_GROUPS => 'getGroups' + ); /** - * @brief get a list of all users in a group - * @returns array with user ids - */ - public static function usersInGroup($gid){} + * @brief Get all supported actions + * @returns bitwise-or'ed actions + * + * Returns the supported actions as int to be + * compared with OC_USER_BACKEND_CREATE_USER etc. + */ + public function getSupportedActions(){ + $actions = 0; + foreach($this->possibleActions AS $action => $methodName){ + if(method_exists($this, $methodName)) { + $actions |= $action; + } + } + + return $actions; + } + + /** + * @brief Check if backend implements actions + * @param $actions bitwise-or'ed actions + * @returns boolean + * + * Returns the supported actions as int to be + * compared with OC_GROUP_BACKEND_CREATE_GROUP etc. + */ + public function implementsActions($actions){ + return (bool)($this->getSupportedActions() & $actions); + } } diff --git a/lib/group/database.php b/lib/group/database.php index 1afd4b5fe4..d401acf43b 100644 --- a/lib/group/database.php +++ b/lib/group/database.php @@ -117,8 +117,10 @@ class OC_Group_Database extends OC_Group_Backend { if( !self::inGroup( $uid, $gid )){ $query = OC_DB::prepare( "INSERT INTO `*PREFIX*group_user` ( `uid`, `gid` ) VALUES( ?, ? )" ); $result = $query->execute( array( $uid, $gid )); + return true; + }else{ + return false; } - return true; } /** diff --git a/lib/group/dummy.php b/lib/group/dummy.php new file mode 100644 index 0000000000..5220237ecb --- /dev/null +++ b/lib/group/dummy.php @@ -0,0 +1,159 @@ +. +* +*/ + +/** + * dummy group backend, does not keep state, only for testing use + */ +class OC_Group_Dummy extends OC_Group_Backend { + private $groups=array(); + /** + * @brief Try to create a new group + * @param $gid The name of the group to create + * @returns true/false + * + * Trys to create a new group. If the group name already exists, false will + * be returned. + */ + public function createGroup($gid){ + if(!isset($this->groups[$gid])){ + $this->groups[$gid]=array(); + return true; + }else{ + return false; + } + } + + /** + * @brief delete a group + * @param $gid gid of the group to delete + * @returns true/false + * + * Deletes a group and removes it from the group_user-table + */ + public function deleteGroup($gid){ + if(isset($this->groups[$gid])){ + unset($this->groups[$gid]); + return true; + }else{ + return false; + } + } + + /** + * @brief is user in group? + * @param $uid uid of the user + * @param $gid gid of the group + * @returns true/false + * + * Checks whether the user is member of a group or not. + */ + public function inGroup($uid, $gid){ + if(isset($this->groups[$gid])){ + return (array_search($uid,$this->groups[$gid])!==false); + }else{ + return false; + } + } + + /** + * @brief Add a user to a group + * @param $uid Name of the user to add to group + * @param $gid Name of the group in which add the user + * @returns true/false + * + * Adds a user to a group. + */ + public function addToGroup($uid, $gid){ + if(isset($this->groups[$gid])){ + if(array_search($uid,$this->groups[$gid])===false){ + $this->groups[$gid][]=$uid; + return true; + }else{ + return false; + } + }else{ + return false; + } + } + + /** + * @brief Removes a user from a group + * @param $uid NameUSER of the user to remove from group + * @param $gid Name of the group from which remove the user + * @returns true/false + * + * removes the user from a group. + */ + public function removeFromGroup($uid,$gid){ + if(isset($this->groups[$gid])){ + if(($index=array_search($uid,$this->groups[$gid]))!==false){ + unset($this->groups[$gid][$index]); + }else{ + return false; + } + }else{ + return false; + } + } + + /** + * @brief Get all groups a user belongs to + * @param $uid Name of the user + * @returns array with group names + * + * This function fetches all groups a user belongs to. It does not check + * if the user exists at all. + */ + public function getUserGroups($uid){ + $groups=array(); + foreach($this->groups as $group=>$user){ + if($this->inGroup($uid,$group)){ + $groups[]=$group; + } + } + return $groups; + } + + /** + * @brief get a list of all groups + * @returns array with group names + * + * Returns a list with all groups + */ + public function getGroups(){ + return array_keys($this->groups); + } + + /** + * @brief get a list of all users in a group + * @returns array with user ids + */ + public function usersInGroup($gid){ + if(isset($this->groups[$gid])){ + return $this->groups[$gid]; + }else{ + return array(); + } + } + +} diff --git a/lib/group/example.php b/lib/group/example.php new file mode 100644 index 0000000000..a88159f91b --- /dev/null +++ b/lib/group/example.php @@ -0,0 +1,102 @@ +. +* +*/ + +/** + * abstract reference class for group management + * this class should only be used as a reference for method signatures and their descriptions + */ +abstract class OC_Group_Example { + /** + * @brief Try to create a new group + * @param $gid The name of the group to create + * @returns true/false + * + * Trys to create a new group. If the group name already exists, false will + * be returned. + */ + public static function createGroup($gid){} + + /** + * @brief delete a group + * @param $gid gid of the group to delete + * @returns true/false + * + * Deletes a group and removes it from the group_user-table + */ + public static function deleteGroup($gid){} + + /** + * @brief is user in group? + * @param $uid uid of the user + * @param $gid gid of the group + * @returns true/false + * + * Checks whether the user is member of a group or not. + */ + public static function inGroup($uid, $gid){} + + /** + * @brief Add a user to a group + * @param $uid Name of the user to add to group + * @param $gid Name of the group in which add the user + * @returns true/false + * + * Adds a user to a group. + */ + public static function addToGroup($uid, $gid){} + + /** + * @brief Removes a user from a group + * @param $uid NameUSER of the user to remove from group + * @param $gid Name of the group from which remove the user + * @returns true/false + * + * removes the user from a group. + */ + public static function removeFromGroup($uid,$gid){} + + /** + * @brief Get all groups a user belongs to + * @param $uid Name of the user + * @returns array with group names + * + * This function fetches all groups a user belongs to. It does not check + * if the user exists at all. + */ + public static function getUserGroups($uid){} + + /** + * @brief get a list of all groups + * @returns array with group names + * + * Returns a list with all groups + */ + public static function getGroups(){} + + /** + * @brief get a list of all users in a group + * @returns array with user ids + */ + public static function usersInGroup($gid){} + +} diff --git a/lib/helper.php b/lib/helper.php old mode 100644 new mode 100755 index 0c6c73aa76..c33db5f10f --- a/lib/helper.php +++ b/lib/helper.php @@ -27,7 +27,7 @@ class OC_Helper { private static $mimetypes=array(); private static $tmpFiles=array(); - + /** * @brief Creates an url * @param $app app @@ -59,6 +59,28 @@ class OC_Helper { return $urlLinkTo; } + /** + * @brief Returns the server host + * @returns the server host + * + * Returns the server host, even if the website uses one or more + * reverse proxies + */ + public static function serverHost() { + if (isset($_SERVER['HTTP_X_FORWARDED_HOST'])) { + if (strpos($_SERVER['HTTP_X_FORWARDED_HOST'], ",") !== false) { + $host = trim(array_pop(explode(",", $_SERVER['HTTP_X_FORWARDED_HOST']))); + } + else{ + $host=$_SERVER['HTTP_X_FORWARDED_HOST']; + } + } + else{ + $host = $_SERVER['HTTP_HOST']; + } + return $host; + } + /** * @brief Creates an absolute url * @param $app app @@ -71,7 +93,7 @@ class OC_Helper { $urlLinkTo = self::linkTo( $app, $file ); // Checking if the request was made through HTTPS. The last in line is for IIS $protocol = isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) && ($_SERVER['HTTPS']!='off'); - $urlLinkTo = ($protocol?'https':'http') . '://' . $_SERVER['HTTP_HOST'] . $urlLinkTo; + $urlLinkTo = ($protocol?'https':'http') . '://' . self::serverHost() . $urlLinkTo; return $urlLinkTo; } @@ -101,7 +123,7 @@ class OC_Helper { }elseif( file_exists( OC::$SERVERROOT."/core/img/$image" )){ return OC::$WEBROOT."/core/img/$image"; }else{ - echo('image not found: image:'.$image.' webroot:'.OC::$WEBROOT.' serverroot:'.OC::$SERVERROOT); + echo('image not found: image:'.$image.' webroot:'.OC::$WEBROOT.' serverroot:'.OC::$SERVERROOT); die(); } } @@ -166,7 +188,7 @@ class OC_Helper { $bytes = round( $bytes / 1024, 1 ); return "$bytes GB"; } - + /** * @brief Make a computer file size * @param $str file size in a fancy format @@ -202,9 +224,9 @@ class OC_Helper { $bytes = round($bytes, 2); - return $bytes; + return $bytes; } - + /** * @brief Recusive editing of file permissions * @param $path path to file or folder @@ -220,7 +242,7 @@ class OC_Helper { $fullpath = $path.'/'.$file; if(is_link($fullpath)) return FALSE; - elseif(!is_dir($fullpath) && !chmod($fullpath, $filemode)) + elseif(!is_dir($fullpath) && !@chmod($fullpath, $filemode)) return FALSE; elseif(!self::chmodr($fullpath, $filemode)) return FALSE; @@ -254,7 +276,7 @@ class OC_Helper { copy($src, $dest); } } - + /** * @brief Recusive deletion of folders * @param string $dir path to the folder @@ -272,6 +294,9 @@ class OC_Helper { }elseif(file_exists($dir)){ unlink($dir); } + if(file_exists($dir)) { + return false; + } } /** @@ -284,12 +309,10 @@ class OC_Helper { $isWrapped=(strpos($path,'://')!==false) and (substr($path,0,7)=='file://'); $mimeType='application/octet-stream'; if ($mimeType=='application/octet-stream') { - if(count(self::$mimetypes)>0){ - self::$mimetypes = include('mimetypes.fixlist.php'); - } - $extention=strtolower(strrchr(basename($path), ".")); - $extention=substr($extention,1);//remove leading . - $mimeType=(isset(self::$mimetypes[$extention]))?self::$mimetypes[$extention]:'application/octet-stream'; + self::$mimetypes = include('mimetypes.fixlist.php'); + $extension=strtolower(strrchr(basename($path), ".")); + $extension=substr($extension,1);//remove leading . + $mimeType=(isset(self::$mimetypes[$extension]))?self::$mimetypes[$extension]:'application/octet-stream'; } if (@is_dir($path)) { @@ -323,13 +346,13 @@ class OC_Helper { if(!self::$mimetypes || self::$mimetypes != include('mimetypes.list.php')){ self::$mimetypes=include('mimetypes.list.php'); } - $extention=strtolower(strrchr(basename($path), ".")); - $extention=substr($extention,1);//remove leading . - $mimeType=(isset(self::$mimetypes[$extention]))?self::$mimetypes[$extention]:'application/octet-stream'; + $extension=strtolower(strrchr(basename($path), ".")); + $extension=substr($extension,1);//remove leading . + $mimeType=(isset(self::$mimetypes[$extension]))?self::$mimetypes[$extension]:'application/octet-stream'; } return $mimeType; } - + /** * @brief Checks $_REQUEST contains a var for the $s key. If so, returns the html-escaped value of this var; otherwise returns the default value provided by $d. * @param $s name of the var to escape, if set. @@ -337,16 +360,16 @@ class OC_Helper { * @returns the print-safe value. * */ - + //FIXME: should also check for value validation (i.e. the email is an email). public static function init_var($s, $d="") { $r = $d; if(isset($_REQUEST[$s]) && !empty($_REQUEST[$s])) $r = stripslashes(htmlspecialchars($_REQUEST[$s])); - + return $r; } - + /** * returns "checked"-attribut if request contains selected radio element OR if radio element is the default one -- maybe? * @param string $s Name of radio-button element name @@ -402,7 +425,7 @@ class OC_Helper { } return false; } - + /** * copy the contents of one stream to another * @param resource source @@ -419,7 +442,7 @@ class OC_Helper { } return $count; } - + /** * create a temporary file with an unique filename * @param string postfix @@ -434,14 +457,38 @@ class OC_Helper { self::$tmpFiles[]=$file; return $file; } - + + /** + * create a temporary folder with an unique filename + * @return string + * + * temporary files are automatically cleaned up after the script is finished + */ + public static function tmpFolder(){ + $path=get_temp_dir().'/'.md5(time().rand()); + mkdir($path); + self::$tmpFiles[]=$path; + return $path.'/'; + } + /** * remove all files created by self::tmpFile */ public static function cleanTmp(){ + $leftoversFile='/tmp/oc-not-deleted'; + if(file_exists($leftoversFile)){ + $leftovers=file($leftoversFile); + foreach($leftovers as $file) { + self::rmdirr($file); + } + unlink($leftoversFile); + } + foreach(self::$tmpFiles as $file){ if(file_exists($file)){ - unlink($file); + if(!self::rmdirr($file)) { + file_put_contents($leftoversFile, $file."\n", FILE_APPEND); + } } } } diff --git a/lib/image.php b/lib/image.php index 60a714880d..4c53dc32f5 100644 --- a/lib/image.php +++ b/lib/image.php @@ -216,7 +216,7 @@ class OC_Image { OC_Log::write('core','OC_Image->fixOrientation() No readable file path set.', OC_Log::DEBUG); return false; } - $exif = @exif_read_data($this->filepath, 'IFD0'); + $exif = @exif_read_data($this->filepath, 'IFD0'); if(!$exif) { return false; } @@ -267,6 +267,7 @@ class OC_Image { if($res) { if(imagealphablending($res, true)) { if(imagesavealpha($res, true)) { + imagedestroy($this->resource); $this->resource = $res; return true; } else { @@ -317,10 +318,7 @@ class OC_Image { */ public function loadFromFileHandle($handle) { OC_Log::write('core',__METHOD__.'(): Trying', OC_Log::DEBUG); - $contents = ''; - while (!feof($handle)) { - $contents .= fread($handle, 8192); - } + $contents = stream_get_contents($handle); if($this->loadFromData($contents)) { return $this->resource; } @@ -486,22 +484,24 @@ class OC_Image { imagedestroy($process); return false; } + imagedestroy($this->resource); $this->resource = $process; return true; } /** * @brief Crops the image to the middle square. If the image is already square it just returns. + * @param int maximum size for the result (optional) * @returns bool for success or failure */ - public function centerCrop() { + public function centerCrop($size=0) { if(!$this->valid()) { OC_Log::write('core','OC_Image->centerCrop, No image loaded', OC_Log::ERROR); return false; } $width_orig=imageSX($this->resource); $height_orig=imageSY($this->resource); - if($width_orig === $height_orig) { + if($width_orig === $height_orig and $size==0) { return true; } $ratio_orig = $width_orig/$height_orig; @@ -514,18 +514,26 @@ class OC_Image { $y = ($height_orig/2) - ($height/2); $x = 0; } - $process = imagecreatetruecolor($width, $height); + if($size>0){ + $targetWidth=$size; + $targetHeight=$size; + }else{ + $targetWidth=$width; + $targetHeight=$height; + } + $process = imagecreatetruecolor($targetWidth, $targetHeight); if ($process == false) { OC_Log::write('core','OC_Image->centerCrop. Error creating true color image',OC_Log::ERROR); imagedestroy($process); return false; } - imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $width, $height, $width, $height); + imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $targetWidth, $targetHeight, $width, $height); if ($process == false) { OC_Log::write('core','OC_Image->centerCrop. Error resampling process image '.$width.'x'.$height,OC_Log::ERROR); imagedestroy($process); return false; } + imagedestroy($this->resource); $this->resource = $process; return true; } @@ -558,7 +566,19 @@ class OC_Image { imagedestroy($process); return false; } + imagedestroy($this->resource); $this->resource = $process; return true; } + + public function destroy(){ + if($this->valid()){ + imagedestroy($this->resource); + } + $this->resource=null; + } + + public function __destruct(){ + $this->destroy(); + } } diff --git a/lib/installer.php b/lib/installer.php index 2a9676998f..6edf4ce1b7 100644 --- a/lib/installer.php +++ b/lib/installer.php @@ -62,7 +62,7 @@ class OC_Installer{ //download the file if necesary if($data['source']=='http'){ - $path=OC_Helper::tmpFile('.zip'); + $path=OC_Helper::tmpFile(); if(!isset($data['href'])){ OC_Log::write('core','No href specified when installing app from http',OC_Log::ERROR); return false; @@ -76,14 +76,24 @@ class OC_Installer{ $path=$data['path']; } + //detect the archive type + $mime=OC_Helper::getMimeType($path); + if($mime=='application/zip'){ + rename($path,$path.'.zip'); + $path.='.zip'; + }elseif($mime=='application/x-gzip'){ + rename($path,$path.'.tgz'); + $path.='.tgz'; + }else{ + OC_Log::write('core','Archives of type '.$mime.' are not supported',OC_Log::ERROR); + return false; + } + //extract the archive in a temporary folder - $extractDir=tempnam(get_temp_dir(),'oc_installer_uncompressed_'); - unlink($extractDir); + $extractDir=OC_Helper::tmpFolder(); mkdir($extractDir); - $zip = new ZipArchive; - if($zip->open($path)===true){ - $zip->extractTo($extractDir); - $zip->close(); + if($archive=OC_Archive::open($path)){ + $archive->extract($extractDir); } else { OC_Log::write('core','Failed to open archive when installing app',OC_Log::ERROR); OC_Helper::rmdirr($extractDir); @@ -94,6 +104,17 @@ class OC_Installer{ } //load the info.xml file of the app + if(!is_file($extractDir.'/appinfo/info.xml')){ + //try to find it in a subdir + $dh=opendir($extractDir); + while($folder=readdir($dh)){ + if(substr($folder,0,1)!='.' and is_dir($extractDir.'/'.$folder)){ + if(is_file($extractDir.'/'.$folder.'/appinfo/info.xml')){ + $extractDir.='/'.$folder; + } + } + } + } if(!is_file($extractDir.'/appinfo/info.xml')){ OC_Log::write('core','App does not provide an info.xml file',OC_Log::ERROR); OC_Helper::rmdirr($extractDir); @@ -102,7 +123,7 @@ class OC_Installer{ } return false; } - $info=OC_App::getAppInfo($extractDir.'/appinfo/info.xml'); + $info=OC_App::getAppInfo($extractDir.'/appinfo/info.xml',true); $basedir=OC::$APPSROOT.'/apps/'.$info['id']; //check if an app with the same id is already installed @@ -154,7 +175,7 @@ class OC_Installer{ } //set the installed version - OC_Appconfig::setValue($info['id'],'installed_version',$info['version']); + OC_Appconfig::setValue($info['id'],'installed_version',OC_App::getAppVersion($info['id'])); OC_Appconfig::setValue($info['id'],'enabled','no'); return $info['id']; } @@ -275,8 +296,8 @@ class OC_Installer{ if(is_file(OC::$APPSROOT."/apps/$app/appinfo/install.php")){ include(OC::$APPSROOT."/apps/$app/appinfo/install.php"); } - $info=OC_App::getAppInfo(OC::$APPSROOT."/apps/$app/appinfo/info.xml"); - OC_Appconfig::setValue($app,'installed_version',$info['version']); + $info=OC_App::getAppInfo($app); + OC_Appconfig::setValue($app,'installed_version',OC_App::getAppVersion($app)); return $info; } } diff --git a/lib/json.php b/lib/json.php index cedf79fd7c..0d208ce12a 100644 --- a/lib/json.php +++ b/lib/json.php @@ -24,7 +24,7 @@ class OC_JSON{ */ public static function checkAppEnabled($app){ if( !OC_App::isEnabled($app)){ - $l = new OC_L10N('core'); + $l = OC_L10N::get('core'); self::error(array( 'data' => array( 'message' => $l->t('Application is not enabled') ))); exit(); } @@ -35,7 +35,7 @@ class OC_JSON{ */ public static function checkLoggedIn(){ if( !OC_User::isLoggedIn()){ - $l = new OC_L10N('core'); + $l = OC_L10N::get('core'); self::error(array( 'data' => array( 'message' => $l->t('Authentication error') ))); exit(); } @@ -47,7 +47,7 @@ class OC_JSON{ public static function checkAdminUser(){ self::checkLoggedIn(); if( !OC_Group::inGroup( OC_User::getUser(), 'admin' )){ - $l = new OC_L10N('core'); + $l = OC_L10N::get('core'); self::error(array( 'data' => array( 'message' => $l->t('Authentication error') ))); exit(); } diff --git a/lib/l10n.php b/lib/l10n.php index 636326f986..c0ecdbd1b7 100644 --- a/lib/l10n.php +++ b/lib/l10n.php @@ -24,6 +24,11 @@ * This class is for i18n and l10n */ class OC_L10N{ + /** + * cached instances + */ + protected static $instances=array(); + /** * cache */ @@ -46,6 +51,21 @@ class OC_L10N{ 'date' => 'd.m.Y', 'datetime' => 'd.m.Y H:i:s', 'time' => 'H:i:s'); + + /** + * get an L10N instance + * @return OC_L10N + */ + public static function get($app,$lang=null){ + if(is_null($lang)){ + if(!isset(self::$instances[$app])){ + self::$instances[$app]=new OC_L10N($app); + } + return self::$instances[$app]; + }else{ + return new OC_L10N($app,$lang); + } + } /** * @brief The constructor @@ -261,17 +281,14 @@ class OC_L10N{ public static function findAvailableLanguages($app=null){ $available=array('en');//english is always available $dir = self::findI18nDir($app); - if(file_exists($dir)){ - $dh = opendir($dir); - while(($file = readdir($dh)) !== false){ - if(substr($file, -4, 4) == '.php' and (strlen($file) == 6 || strlen($file) == 9)){ + if(is_dir($dir)){ + $files=scandir($dir); + foreach($files as $file){ + if(substr($file, -4, 4) == '.php'){ $i = substr($file, 0, -4); - if($i != ''){ - $available[] = $i; - } + $available[] = $i; } } - closedir($dh); } return $available; } diff --git a/lib/log.php b/lib/log.php index 4e450a027f..8bb2839be6 100644 --- a/lib/log.php +++ b/lib/log.php @@ -1,78 +1,39 @@ . - * + * Copyright (c) 2012 Bart Visscher + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. */ /** - *logging utilities + * logging utilities * - * Log is saved at data/owncloud.log (on default) + * Log is saved by default at data/owncloud.log using OC_Log_Owncloud. + * Selecting other backend is done with a config option 'log_type'. */ -class OC_Log{ +class OC_Log { const DEBUG=0; const INFO=1; const WARN=2; const ERROR=3; const FATAL=4; + static protected $class = null; + /** * write a message in the log * @param string $app * @param string $message * @param int level */ - public static function write($app,$message,$level){ - $minLevel=OC_Config::getValue( "loglevel", 2 ); - if($level>=$minLevel){ - $datadir=OC_Config::getValue( "datadirectory", OC::$SERVERROOT.'/data' ); - $logFile=OC_Config::getValue( "logfile", $datadir.'/owncloud.log' ); - $entry=array('app'=>$app,'message'=>$message,'level'=>$level,'time'=>time()); - $fh=fopen($logFile,'a'); - fwrite($fh,json_encode($entry)."\n"); - fclose($fh); + public static function write($app, $message, $level) { + if (!self::$class) { + self::$class = 'OC_Log_'.ucfirst(OC_Config::getValue('log_type', 'owncloud')); + call_user_func(array(self::$class, 'init')); } - } - - /** - * get entries from the log in reverse chronological order - * @param int limit - * @param int offset - * @return array - */ - public static function getEntries($limit=50,$offset=0){ - $datadir=OC_Config::getValue( "datadirectory", OC::$SERVERROOT.'/data' ); - $logFile=OC_Config::getValue( "logfile", $datadir.'/owncloud.log' ); - $entries=array(); - if(!file_exists($logFile)){ - return array(); - } - $contents=file($logFile); - if(!$contents){//error while reading log - return array(); - } - $end=max(count($contents)-$offset-1,0); - $start=max($end-$limit,0); - for($i=$end;$i>$start;$i--){ - $entries[]=json_decode($contents[$i]); - } - return $entries; + $log_class=self::$class; + $log_class::write($app, $message, $level); } } diff --git a/lib/log/owncloud.php b/lib/log/owncloud.php new file mode 100644 index 0000000000..0ed3051013 --- /dev/null +++ b/lib/log/owncloud.php @@ -0,0 +1,79 @@ +. + * + */ + +/** + * logging utilities + * + * Log is saved at data/owncloud.log (on default) + */ + +class OC_Log_Owncloud { + static protected $logFile; + + /** + * Init class data + */ + public static function init() { + $datadir=OC_Config::getValue( "datadirectory", OC::$SERVERROOT.'/data' ); + self::$logFile=OC_Config::getValue( "logfile", $datadir.'/owncloud.log' ); + } + + /** + * write a message in the log + * @param string $app + * @param string $message + * @param int level + */ + public static function write($app, $message, $level) { + $minLevel=OC_Config::getValue( "loglevel", 2 ); + if($level>=$minLevel){ + $entry=array('app'=>$app, 'message'=>$message, 'level'=>$level,'time'=>time()); + $fh=fopen(self::$logFile, 'a'); + fwrite($fh, json_encode($entry)."\n"); + fclose($fh); + } + } + + /** + * get entries from the log in reverse chronological order + * @param int limit + * @param int offset + * @return array + */ + public static function getEntries($limit=50, $offset=0){ + self::init(); + $entries=array(); + if(!file_exists(self::$logFile)) { + return array(); + } + $contents=file(self::$logFile); + if(!$contents) {//error while reading log + return array(); + } + $end=max(count($contents)-$offset-1, 0); + $start=max($end-$limit,0); + for($i=$end;$i>$start;$i--) { + $entries[]=json_decode($contents[$i]); + } + return $entries; + } +} diff --git a/lib/log/syslog.php b/lib/log/syslog.php new file mode 100644 index 0000000000..d1fb28d8b0 --- /dev/null +++ b/lib/log/syslog.php @@ -0,0 +1,37 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class OC_Log_Syslog { + static protected $levels = array( + OC_Log::DEBUG => LOG_DEBUG, + OC_Log::INFO => LOG_INFO, + OC_Log::WARN => LOG_WARNING, + OC_Log::ERROR => LOG_ERR, + OC_Log::FATAL => LOG_CRIT, + ); + + /** + * Init class data + */ + public static function init() { + openlog('ownCloud', LOG_PID | LOG_CONS, LOG_USER); + // Close at shutdown + register_shutdown_function('closelog'); + } + + /** + * write a message in the log + * @param string $app + * @param string $message + * @param int level + */ + public static function write($app, $message, $level) { + $syslog_level = self::$levels[$level]; + syslog($syslog_level, '{'.$app.'} '.$message); + } +} diff --git a/lib/migrate.php b/lib/migrate.php new file mode 100644 index 0000000000..f46d860e80 --- /dev/null +++ b/lib/migrate.php @@ -0,0 +1,715 @@ +. + * + */ + + +/** + * provides an interface to migrate users and whole ownclouds + */ +class OC_Migrate{ + + + // Array of OC_Migration_Provider objects + static private $providers=array(); + // User id of the user to import/export + static private $uid=false; + // Holds the ZipArchive object + static private $zip=false; + // Stores the type of export + static private $exporttype=false; + // Array of temp files to be deleted after zip creation + static private $tmpfiles=array(); + // Holds the db object + static private $MDB2=false; + // Schema db object + static private $schema=false; + // Path to the sqlite db + static private $dbpath=false; + // Holds the path to the zip file + static private $zippath=false; + // Holds the OC_Migration_Content object + static private $content=false; + + /** + * register a new migration provider + * @param OC_Migrate_Provider $provider + */ + public static function registerProvider($provider){ + self::$providers[]=$provider; + } + + /** + * @brief finds and loads the providers + */ + static private function findProviders(){ + // Find the providers + $apps = OC_App::getAllApps(); + + foreach($apps as $app){ + $path = OC::$SERVERROOT . '/apps/' . $app . '/appinfo/migrate.php'; + if( file_exists( $path ) ){ + include( $path ); + } + } + } + + /** + * @brief exports a user, or owncloud instance + * @param optional $uid string user id of user to export if export type is user, defaults to current + * @param ootional $type string type of export, defualts to user + * @param otional $path string path to zip output folder + * @return false on error, path to zip on success + */ + public static function export( $uid=null, $type='user', $path=null ){ + $datadir = OC_Config::getValue( 'datadirectory' ); + // Validate export type + $types = array( 'user', 'instance', 'system', 'userfiles' ); + if( !in_array( $type, $types ) ){ + OC_Log::write( 'migration', 'Invalid export type', OC_Log::ERROR ); + return json_encode( array( array( 'success' => false ) ) ); + } + self::$exporttype = $type; + // Userid? + if( self::$exporttype == 'user' ){ + // Check user exists + if( !is_null($uid) ){ + $db = new OC_User_Database; + if( !$db->userExists( $uid ) ){ + OC_Log::write('migration', 'User: '.$uid.' is not in the database and so cannot be exported.', OC_Log::ERROR); + return json_encode( array( 'success' => false ) ); + } + self::$uid = $uid; + } else { + self::$uid = OC_User::getUser(); + } + } + // Calculate zipname + if( self::$exporttype == 'user' ){ + $zipname = 'oc_export_' . self::$uid . '_' . date("y-m-d_H-i-s") . '.zip'; + } else { + $zipname = 'oc_export_' . self::$exporttype . '_' . date("y-m-d_H-i-s") . '.zip'; + } + // Calculate path + if( self::$exporttype == 'user' ){ + self::$zippath = $datadir . '/' . self::$uid . '/' . $zipname; + } else { + if( !is_null( $path ) ){ + // Validate custom path + if( !file_exists( $path ) || !is_writeable( $path ) ){ + OC_Log::write( 'migration', 'Path supplied is invalid.', OC_Log::ERROR ); + return json_encode( array( 'success' => false ) ); + } + self::$zippath = $path . $zipname; + } else { + // Default path + self::$zippath = get_temp_dir() . '/' . $zipname; + } + } + // Create the zip object + if( !self::createZip() ){ + return json_encode( array( 'success' => false ) ); + } + // Do the export + self::findProviders(); + $exportdata = array(); + switch( self::$exporttype ){ + case 'user': + // Connect to the db + self::$dbpath = $datadir . '/' . self::$uid . '/migration.db'; + if( !self::connectDB() ){ + return json_encode( array( 'success' => false ) ); + } + self::$content = new OC_Migration_Content( self::$zip, self::$MDB2 ); + // Export the app info + $exportdata = self::exportAppData(); + // Add the data dir to the zip + self::$content->addDir( $datadir . '/' . self::$uid, true, '/' ); + break; + case 'instance': + self::$content = new OC_Migration_Content( self::$zip ); + // Creates a zip that is compatable with the import function + $dbfile = tempnam( "/tmp", "owncloud_export_data_" ); + OC_DB::getDbStructure( $dbfile, 'MDB2_SCHEMA_DUMP_ALL'); + + // Now add in *dbname* and *dbprefix* + $dbexport = file_get_contents( $dbfile ); + $dbnamestring = "\n\n " . OC_Config::getValue( "dbname", "owncloud" ); + $dbtableprefixstring = "\n\n " . OC_Config::getValue( "dbtableprefix", "oc_" ); + $dbexport = str_replace( $dbnamestring, "\n\n *dbname*", $dbexport ); + $dbexport = str_replace( $dbtableprefixstring, "
    \n\n *dbprefix*", $dbexport ); + // Add the export to the zip + self::$content->addFromString( $dbexport, "dbexport.xml" ); + // Add user data + foreach(OC_User::getUsers() as $user){ + self::$content->addDir( $datadir . '/' . $user . '/', true, "/userdata/" ); + } + break; + case 'userfiles': + self::$content = new OC_Migration_Content( self::$zip ); + // Creates a zip with all of the users files + foreach(OC_User::getUsers() as $user){ + self::$content->addDir( $datadir . '/' . $user . '/', true, "/" ); + } + break; + case 'system': + self::$content = new OC_Migration_Content( self::$zip ); + // Creates a zip with the owncloud system files + self::$content->addDir( OC::$SERVERROOT . '/', false, '/'); + foreach (array(".git", "3rdparty", "apps", "core", "files", "l10n", "lib", "ocs", "search", "settings", "tests") as $dir) { + self::$content->addDir( OC::$SERVERROOT . '/' . $dir, true, "/"); + } + break; + } + if( !$info = self::getExportInfo( $exportdata ) ){ + return json_encode( array( 'success' => false ) ); + } + // Add the export info json to the export zip + self::$content->addFromString( $info, 'export_info.json' ); + if( !self::$content->finish() ){ + return json_encode( array( 'success' => false ) ); + } + return json_encode( array( 'success' => true, 'data' => self::$zippath ) ); + } + + /** + * @brief imports a user, or owncloud instance + * @param $path string path to zip + * @param optional $type type of import (user or instance) + * @param optional $uid userid of new user + */ + public static function import( $path, $type='user', $uid=null ){ + OC_Util::checkAdminUser(); + $datadir = OC_Config::getValue( 'datadirectory' ); + // Extract the zip + if( !$extractpath = self::extractZip( $path ) ){ + return json_encode( array( 'success' => false ) ); + } + // Get export_info.json + $scan = scandir( $extractpath ); + // Check for export_info.json + if( !in_array( 'export_info.json', $scan ) ){ + OC_Log::write( 'migration', 'Invalid import file, export_info.json note found', OC_Log::ERROR ); + return json_encode( array( 'success' => false ) ); + } + $json = json_decode( file_get_contents( $extractpath . 'export_info.json' ) ); + if( $json->exporttype != $type ){ + OC_Log::write( 'migration', 'Invalid import file', OC_Log::ERROR ); + return json_encode( array( 'success' => false ) ); + } + self::$exporttype = $type; + + // Have we got a user if type is user + if( self::$exporttype == 'user' ){ + if( !$uid ){ + self::$uid = $json->exporteduser; + } else { + self::$uid = $uid; + } + } + + // Handle export types + switch( self::$exporttype ){ + case 'user': + // Check user availability + if( OC_User::userExists( self::$uid ) ){ + OC_Log::write( 'migration', 'User already exists', OC_Log::ERROR ); + return json_encode( array( 'success' => false ) ); + } + $run = true; + OC_Hook::emit( "OC_User", "pre_createUser", array( "run" => &$run, "uid" => self::$uid, "password" => $json->hash )); + if( !$run ){ + // Something stopped the user creation + OC_Log::write( 'migration', 'User creation failed', OC_Log::ERROR ); + return json_encode( array( 'success' => false ) ); + } + // Create the user + if( !self::createUser( self::$uid, $json->hash ) ){ + return json_encode( array( 'success' => false ) ); + } + // Emit the post_createUser hook (password is already hashed, will cause problems + OC_Hook::emit( "OC_User", "post_createUser", array( "uid" => self::$uid, "password" => $json->hash )); + // Make the new users data dir + $path = $datadir . '/' . self::$uid; + if( !mkdir( $path, 0755, true ) ){ + OC_Log::write( 'migration', 'Failed to create users data dir: '.$path, OC_Log::ERROR ); + return json_encode( array( 'success' => false ) ); + } + // Copy data + if( !self::copy_r( $extractpath . $json->exporteduser, $datadir . '/' . self::$uid ) ){ + return json_encode( array( 'success' => false ) ); + } + // Import user app data + if( !$appsimported = self::importAppData( $extractpath . $json->exporteduser . '/migration.db', $json, self::$uid ) ){ + return json_encode( array( 'success' => false ) ); + } + // All done! + if( !self::unlink_r( $extractpath ) ){ + OC_Log::write( 'migration', 'Failed to delete the extracted zip', OC_Log::ERROR ); + } + return json_encode( array( 'success' => true, 'data' => $appsimported ) ); + break; + case 'instance': + /* + * EXPERIMENTAL + // Check for new data dir and dbexport before doing anything + // TODO + + // Delete current data folder. + OC_Log::write( 'migration', "Deleting current data dir", OC_Log::INFO ); + if( !self::unlink_r( $datadir, false ) ){ + OC_Log::write( 'migration', 'Failed to delete the current data dir', OC_Log::ERROR ); + return json_encode( array( 'success' => false ) ); + } + + // Copy over data + if( !self::copy_r( $extractpath . 'userdata', $datadir ) ){ + OC_Log::write( 'migration', 'Failed to copy over data directory', OC_Log::ERROR ); + return json_encode( array( 'success' => false ) ); + } + + // Import the db + if( !OC_DB::replaceDB( $extractpath . 'dbexport.xml' ) ){ + return json_encode( array( 'success' => false ) ); + } + // Done + return json_encode( 'success' => true ); + */ + break; + } + + } + + /** + * @brief recursively deletes a directory + * @param $dir string path of dir to delete + * $param optional $deleteRootToo bool delete the root directory + * @return bool + */ + private static function unlink_r( $dir, $deleteRootToo=true ){ + if( !$dh = @opendir( $dir ) ){ + return false; + } + while (false !== ($obj = readdir($dh))){ + if($obj == '.' || $obj == '..') { + continue; + } + if (!@unlink($dir . '/' . $obj)){ + self::unlink_r($dir.'/'.$obj, true); + } + } + closedir($dh); + if ( $deleteRootToo ) { + @rmdir($dir); + } + return true; + } + + /** + * @brief copies recursively + * @param $path string path to source folder + * @param $dest string path to destination + * @return bool + */ + private static function copy_r( $path, $dest ){ + if( is_dir($path) ){ + @mkdir( $dest ); + $objects = scandir( $path ); + if( sizeof( $objects ) > 0 ){ + foreach( $objects as $file ){ + if( $file == "." || $file == ".." ) + continue; + // go on + if( is_dir( $path . '/' . $file ) ){ + self::copy_r( $path .'/' . $file, $dest . '/' . $file ); + } else { + copy( $path . '/' . $file, $dest . '/' . $file ); + } + } + } + return true; + } + elseif( is_file( $path ) ){ + return copy( $path, $dest ); + } else { + return false; + } + } + + /** + * @brief tries to extract the import zip + * @param $path string path to the zip + * @return string path to extract location (with a trailing slash) or false on failure + */ + static private function extractZip( $path ){ + self::$zip = new ZipArchive; + // Validate path + if( !file_exists( $path ) ){ + OC_Log::write( 'migration', 'Zip not found', OC_Log::ERROR ); + return false; + } + if ( self::$zip->open( $path ) != TRUE ) { + OC_Log::write( 'migration', "Failed to open zip file", OC_Log::ERROR ); + return false; + } + $to = get_temp_dir() . '/oc_import_' . self::$exporttype . '_' . date("y-m-d_H-i-s") . '/'; + if( !self::$zip->extractTo( $to ) ){ + return false; + } + self::$zip->close(); + return $to; + } + + /** + * @brief connects to a MDB2 database scheme + * @returns bool + */ + static private function connectScheme(){ + // We need a mdb2 database connection + self::$MDB2->loadModule( 'Manager' ); + self::$MDB2->loadModule( 'Reverse' ); + + // Connect if this did not happen before + if( !self::$schema ){ + require_once('MDB2/Schema.php'); + self::$schema=MDB2_Schema::factory( self::$MDB2 ); + } + + return true; + } + + /** + * @brief creates a migration.db in the users data dir with their app data in + * @return bool whether operation was successfull + */ + private static function exportAppData( ){ + + $success = true; + $return = array(); + + // Foreach provider + foreach( self::$providers as $provider ){ + $success = true; + // Does this app use the database? + if( file_exists( OC::$SERVERROOT.'/apps/'.$provider->getID().'/appinfo/database.xml' ) ){ + // Create some app tables + $tables = self::createAppTables( $provider->getID() ); + if( is_array( $tables ) ){ + // Save the table names + foreach($tables as $table){ + $return['apps'][$provider->getID()]['tables'][] = $table; + } + } else { + // It failed to create the tables + $success = false; + } + } + + // Run the export function? + if( $success ){ + // Set the provider properties + $provider->setData( self::$uid, self::$content ); + $return['apps'][$provider->getID()]['success'] = $provider->export(); + } else { + $return['apps'][$provider->getID()]['success'] = false; + $return['apps'][$provider->getID()]['message'] = 'failed to create the app tables'; + } + + // Now add some app info the the return array + $appinfo = OC_App::getAppInfo( $provider->getID() ); + $return['apps'][$provider->getID()]['version'] = OC_App::getAppVersion($provider->getID()); + + } + + return $return; + + } + + + /** + * @brief generates json containing export info, and merges any data supplied + * @param optional $array array of data to include in the returned json + * @return bool + */ + static private function getExportInfo( $array=array() ){ + $info = array( + 'ocversion' => OC_Util::getVersion(), + 'exporttime' => time(), + 'exportedby' => OC_User::getUser(), + 'exporttype' => self::$exporttype + ); + // Add hash if user export + if( self::$exporttype == 'user' ){ + $query = OC_DB::prepare( "SELECT password FROM *PREFIX*users WHERE uid LIKE ?" ); + $result = $query->execute( array( self::$uid ) ); + $row = $result->fetchRow(); + $hash = $row ? $row['password'] : false; + if( !$hash ){ + OC_Log::write( 'migration', 'Failed to get the users password hash', OC_log::ERROR); + return false; + } + $info['hash'] = $hash; + $info['exporteduser'] = self::$uid; + } + if( !is_array( $array ) ){ + OC_Log::write( 'migration', 'Supplied $array was not an array in getExportInfo()', OC_Log::ERROR ); + } + // Merge in other data + $info = array_merge( $info, (array)$array ); + // Create json + $json = json_encode( $info ); + return $json; + } + + /** + * @brief connects to migration.db, or creates if not found + * @param $db optional path to migration.db, defaults to user data dir + * @return bool whether the operation was successful + */ + static private function connectDB( $path=null ){ + // Has the dbpath been set? + self::$dbpath = !is_null( $path ) ? $path : self::$dbpath; + if( !self::$dbpath ){ + OC_Log::write( 'migration', 'connectDB() was called without dbpath being set', OC_Log::ERROR ); + return false; + } + // Already connected + if(!self::$MDB2){ + require_once('MDB2.php'); + + $datadir = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ); + + // DB type + if( class_exists( 'SQLite3' ) ){ + $dbtype = 'sqlite3'; + } else if( is_callable( 'sqlite_open' ) ){ + $dbtype = 'sqlite'; + } else { + OC_Log::write( 'migration', 'SQLite not found', OC_Log::ERROR ); + return false; + } + + // Prepare options array + $options = array( + 'portability' => MDB2_PORTABILITY_ALL & (!MDB2_PORTABILITY_FIX_CASE), + 'log_line_break' => '
    ', + 'idxname_format' => '%s', + 'debug' => true, + 'quote_identifier' => true + ); + $dsn = array( + 'phptype' => $dbtype, + 'database' => self::$dbpath, + 'mode' => '0644' + ); + + // Try to establish connection + self::$MDB2 = MDB2::factory( $dsn, $options ); + // Die if we could not connect + if( PEAR::isError( self::$MDB2 ) ){ + die( self::$MDB2->getMessage() ); + OC_Log::write( 'migration', 'Failed to create/connect to migration.db', OC_Log::FATAL ); + OC_Log::write( 'migration', self::$MDB2->getUserInfo(), OC_Log::FATAL ); + OC_Log::write( 'migration', self::$MDB2->getMessage(), OC_Log::FATAL ); + return false; + } + // We always, really always want associative arrays + self::$MDB2->setFetchMode(MDB2_FETCHMODE_ASSOC); + } + return true; + + } + + /** + * @brief creates the tables in migration.db from an apps database.xml + * @param $appid string id of the app + * @return bool whether the operation was successful + */ + static private function createAppTables( $appid ){ + + if( !self::connectScheme() ){ + return false; + } + + // There is a database.xml file + $content = file_get_contents( OC::$SERVERROOT . '/apps/' . $appid . '/appinfo/database.xml' ); + + $file2 = 'static://db_scheme'; + // TODO get the relative path to migration.db from the data dir + // For now just cheat + $path = pathinfo( self::$dbpath ); + $content = str_replace( '*dbname*', self::$uid.'/migration', $content ); + $content = str_replace( '*dbprefix*', '', $content ); + + $xml = new SimpleXMLElement($content); + foreach($xml->table as $table){ + $tables[] = (string)$table->name; + } + + file_put_contents( $file2, $content ); + + // Try to create tables + $definition = self::$schema->parseDatabaseDefinitionFile( $file2 ); + + unlink( $file2 ); + + // Die in case something went wrong + if( $definition instanceof MDB2_Schema_Error ){ + OC_Log::write( 'migration', 'Failed to parse database.xml for: '.$appid, OC_Log::FATAL ); + OC_Log::write( 'migration', $definition->getMessage().': '.$definition->getUserInfo(), OC_Log::FATAL ); + return false; + } + + $definition['overwrite'] = true; + + $ret = self::$schema->createDatabase( $definition ); + + // Die in case something went wrong + if( $ret instanceof MDB2_Error ){ + OC_Log::write( 'migration', 'Failed to create tables for: '.$appid, OC_Log::FATAL ); + OC_Log::write( 'migration', $ret->getMessage().': '.$ret->getUserInfo(), OC_Log::FATAL ); + return false; + } + return $tables; + + } + + /** + * @brief tries to create the zip + * @param $path string path to zip destination + * @return bool + */ + static private function createZip(){ + self::$zip = new ZipArchive; + // Check if properties are set + if( !self::$zippath ){ + OC_Log::write('migration', 'createZip() called but $zip and/or $zippath have not been set', OC_Log::ERROR); + return false; + } + if ( self::$zip->open( self::$zippath, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE ) !== TRUE ) { + OC_Log::write('migration', 'Failed to create the zip with error: '.self::$zip->getStatusString(), OC_Log::ERROR); + return false; + } else { + return true; + } + } + + /** + * @brief returns an array of apps that support migration + * @return array + */ + static public function getApps(){ + $allapps = OC_App::getAllApps(); + foreach($allapps as $app){ + $path = OC::$SERVERROOT . '/apps/' . $app . '/lib/migrate.php'; + if( file_exists( $path ) ){ + $supportsmigration[] = $app; + } + } + return $supportsmigration; + } + + /** + * @brief imports a new user + * @param $db string path to migration.db + * @param $info object of migration info + * @param $uid optional uid to use + * @return array of apps with import statuses, or false on failure. + */ + public static function importAppData( $db, $info, $uid=null ){ + // Check if the db exists + if( file_exists( $db ) ){ + // Connect to the db + if(!self::connectDB( $db )){ + OC_Log::write('migration','Failed to connect to migration.db',OC_Log::ERROR); + return false; + } + } else { + OC_Log::write('migration','Migration.db not found at: '.$db, OC_Log::FATAL ); + return false; + } + + // Find providers + self::findProviders(); + + // Generate importinfo array + $importinfo = array( + 'olduid' => $info->exporteduser, + 'newuid' => self::$uid + ); + + foreach( self::$providers as $provider){ + // Is the app in the export? + $id = $provider->getID(); + if( isset( $info->apps->$id ) ){ + // Is the app installed + if( !OC_App::isEnabled( $id ) ){ + OC_Log::write( 'migration', 'App: ' . $id . ' is not installed, can\'t import data.', OC_Log::INFO ); + $appsstatus[$id] = 'notsupported'; + } else { + // Did it succeed on export? + if( $info->apps->$id->success ){ + // Give the provider the content object + if( !self::connectDB( $db ) ){ + return false; + } + $content = new OC_Migration_Content( self::$zip, self::$MDB2 ); + $provider->setData( self::$uid, $content, $info ); + // Then do the import + if( !$appsstatus[$id] = $provider->import( $info->apps->$id, $importinfo ) ){ + // Failed to import app + OC_Log::write( 'migration', 'Failed to import app data for user: ' . self::$uid . ' for app: ' . $id, OC_Log::ERROR ); + } + } else { + // Add to failed list + $appsstatus[$id] = false; + } + } + } + } + + return $appsstatus; + + } + + /* + * @brief creates a new user in the database + * @param $uid string user_id of the user to be created + * @param $hash string hash of the user to be created + * @return bool result of user creation + */ + public static function createUser( $uid, $hash ){ + + // Check if userid exists + if(OC_User::userExists( $uid )){ + return false; + } + + // Create the user + $query = OC_DB::prepare( "INSERT INTO `*PREFIX*users` ( `uid`, `password` ) VALUES( ?, ? )" ); + $result = $query->execute( array( $uid, $hash)); + if( !$result ){ + OC_Log::write('migration', 'Failed to create the new user "'.$uid.""); + } + return $result ? true : false; + + } + +} diff --git a/lib/migration/content.php b/lib/migration/content.php new file mode 100644 index 0000000000..7ef88f36e4 --- /dev/null +++ b/lib/migration/content.php @@ -0,0 +1,252 @@ +. + * + */ + + +/** + * provides methods to add and access data from the migration + */ +class OC_Migration_Content{ + + private $zip=false; + // Holds the MDB2 object + private $db=null; + // Holds an array of tmpfiles to delete after zip creation + private $tmpfiles=false; + + /** + * @brief sets up the + * @param $zip ZipArchive object + * @param optional $db a MDB2 database object (required for exporttype user) + * @return bool + */ + public function __construct( $zip, $db=null ){ + + $this->zip = $zip; + $this->db = $db; + + if( !is_null( $db ) ){ + // Get db path + $db = $this->db->getDatabase(); + $this->tmpfiles[] = $db; + } + + } + + // @brief prepares the db + // @param $query the sql query to prepare + public function prepare( $query ){ + + // Optimize the query + $query = $this->processQuery( $query ); + + // Optimize the query + $query = $this->db->prepare( $query ); + + // Die if we have an error (error means: bad query, not 0 results!) + if( PEAR::isError( $query ) ) { + $entry = 'DB Error: "'.$result->getMessage().'"
    '; + $entry .= 'Offending command was: '.$query.'
    '; + OC_Log::write( 'migration', $entry, OC_Log::FATAL ); + return false; + } else { + return $query; + } + + } + + /** + * @brief processes the db query + * @param $query the query to process + * @return string of processed query + */ + private function processQuery( $query ){ + $query = str_replace( '`', '\'', $query ); + $query = str_replace( 'NOW()', 'datetime(\'now\')', $query ); + $query = str_replace( 'now()', 'datetime(\'now\')', $query ); + // remove table prefixes + $query = str_replace( '*PREFIX*', '', $query ); + return $query; + } + + /** + * @brief copys rows to migration.db from the main database + * @param $options array of options. + * @return bool + */ + public function copyRows( $options ){ + if( !array_key_exists( 'table', $options ) ){ + return false; + } + + $return = array(); + + // Need to include 'where' in the query? + if( array_key_exists( 'matchval', $options ) && array_key_exists( 'matchcol', $options ) ){ + + // If only one matchval, create an array + if(!is_array($options['matchval'])){ + $options['matchval'] = array( $options['matchval'] ); + } + + foreach( $options['matchval'] as $matchval ){ + // Run the query for this match value (where x = y value) + $sql = "SELECT * FROM *PREFIX*" . $options['table'] . " WHERE " . $options['matchcol'] . " LIKE ?"; + $query = OC_DB::prepare( $sql ); + $results = $query->execute( array( $matchval ) ); + $newreturns = $this->insertData( $results, $options ); + $return = array_merge( $return, $newreturns ); + } + + } else { + // Just get everything + $sql = "SELECT * FROM *PREFIX*" . $options['table']; + $query = OC_DB::prepare( $sql ); + $results = $query->execute(); + $return = $this->insertData( $results, $options ); + + } + + return $return; + + } + + /** + * @brief saves a sql data set into migration.db + * @param $data a sql data set returned from self::prepare()->query() + * @param $options array of copyRows options + * @return void + */ + private function insertData( $data, $options ){ + $return = array(); + // Foreach row of data to insert + while( $row = $data->fetchRow() ){ + // Now save all this to the migration.db + foreach($row as $field=>$value){ + $fields[] = $field; + $values[] = $value; + } + + // Generate some sql + $sql = "INSERT INTO `" . $options['table'] . '` ( `'; + $fieldssql = implode( '`, `', $fields ); + $sql .= $fieldssql . "` ) VALUES( "; + $valuessql = substr( str_repeat( '?, ', count( $fields ) ),0,-2 ); + $sql .= $valuessql . " )"; + // Make the query + $query = $this->prepare( $sql ); + if( !$query ){ + OC_Log::write( 'migration', 'Invalid sql produced: '.$sql, OC_Log::FATAL ); + return false; + exit(); + } else { + $query->execute( $values ); + // Do we need to return some values? + if( array_key_exists( 'idcol', $options ) ){ + // Yes we do + $return[] = $row[$options['idcol']]; + } else { + // Take a guess and return the first field :) + $return[] = reset($row); + } + } + $fields = ''; + $values = ''; + } + return $return; + } + + /** + * @brief adds a directory to the zip object + * @param $dir string path of the directory to add + * @param $recursive bool + * @param $internaldir string path of folder to add dir to in zip + * @return bool + */ + public function addDir( $dir, $recursive=true, $internaldir='' ) { + $dirname = basename($dir); + $this->zip->addEmptyDir($internaldir . $dirname); + $internaldir.=$dirname.='/'; + if( !file_exists( $dir ) ){ + return false; + } + if ($dirhandle = opendir($dir)) { + while (false !== ( $file = readdir($dirhandle))) { + + if (( $file != '.' ) && ( $file != '..' )) { + + if (is_dir($dir . '/' . $file) && $recursive) { + $this->addDir($dir . '/' . $file, $recursive, $internaldir); + } elseif (is_file($dir . '/' . $file)) { + $this->zip->addFile($dir . '/' . $file, $internaldir . $file); + } + } + } + closedir($dirhandle); + } else { + OC_Log::write('admin_export',"Was not able to open directory: " . $dir,OC_Log::ERROR); + return false; + } + return true; + } + + /** + * @brief adds a file to the zip from a given string + * @param $data string of data to add + * @param $path the relative path inside of the zip to save the file to + * @return bool + */ + public function addFromString( $data, $path ){ + // Create a temp file + $file = tempnam( get_temp_dir(). '/', 'oc_export_tmp_' ); + $this->tmpfiles[] = $file; + if( !file_put_contents( $file, $data ) ){ + OC_Log::write( 'migation', 'Failed to save data to a temporary file', OC_Log::ERROR ); + return false; + } + // Add file to the zip + $this->zip->addFile( $file, $path ); + return true; + } + + /** + * @brief closes the zip, removes temp files + * @return bool + */ + public function finish(){ + if( !$this->zip->close() ){ + OC_Log::write( 'migration', 'Failed to write the zip file with error: '.$this->zip->getStatusString(), OC_Log::ERROR ); + return false; + } + $this->cleanup(); + return true; + } + + /** + * @brief cleans up after the zip + */ + private function cleanup(){ + // Delete tmp files + foreach($this->tmpfiles as $i){ + unlink( $i ); + } + } +} diff --git a/lib/migration/provider.php b/lib/migration/provider.php new file mode 100644 index 0000000000..91336f3019 --- /dev/null +++ b/lib/migration/provider.php @@ -0,0 +1,52 @@ +id = $appid; + OC_Migrate::registerProvider( $this ); + } + + /** + * @brief exports data for apps + * @return array appdata to be exported + */ + abstract function export( ); + + /** + * @brief imports data for the app + * @return void + */ + abstract function import( ); + + /** + * @brief sets the OC_Migration_Content object to $this->content + * @param $content a OC_Migration_Content object + */ + public function setData( $uid, $content, $info=null ){ + $this->content = $content; + $this->uid = $uid; + $id = $this->id; + if( !is_null( $info ) ){ + $this->olduid = $info->exporteduser; + $this->appinfo = $info->apps->$id; + } + } + + /** + * @brief returns the appid of the provider + * @return string + */ + public function getID(){ + return $this->id; + } +} diff --git a/lib/mimetypes.fixlist.php b/lib/mimetypes.fixlist.php index 1c6acbc443..a40fbd9e22 100644 --- a/lib/mimetypes.fixlist.php +++ b/lib/mimetypes.fixlist.php @@ -10,5 +10,12 @@ return array( 'pl'=>'text/x-script.perl', 'py'=>'text/x-script.phyton', 'vcf' => 'text/vcard', - 'vcard' => 'text/vcard' + 'vcard' => 'text/vcard', + 'doc'=>'application/msword', + 'docx'=>'application/msword', + 'xls'=>'application/msexcel', + 'xlsx'=>'application/msexcel', + 'ppt'=>'application/mspowerpoint', + 'pptx'=>'application/mspowerpoint', + 'sgf' => 'application/sgf' ); diff --git a/lib/mimetypes.list.php b/lib/mimetypes.list.php index e0570e84ea..ccf47999b1 100644 --- a/lib/mimetypes.list.php +++ b/lib/mimetypes.list.php @@ -21,7 +21,7 @@ */ /** - * list of mimetypes by extention + * list of mimetypes by extension */ return array( diff --git a/lib/ocsclient.php b/lib/ocsclient.php old mode 100644 new mode 100755 index 9d5932fb72..d830a4f3e7 --- a/lib/ocsclient.php +++ b/lib/ocsclient.php @@ -28,6 +28,38 @@ class OC_OCSClient{ + /** + * @brief Get the url of the OCS AppStore server. + * @returns string of the AppStore server + * + * This function returns the url of the OCS AppStore server. It´s possible to set it in the config file or it will fallback to the default + */ + private static function getAppStoreURL(){ + $configurl=OC_Config::getValue('appstoreurl', ''); + if($configurl<>'') { + $url=$configurl; + }else{ + $url='http://api.apps.owncloud.com/v1'; + } + return($url); + } + + /** + * @brief Get the url of the OCS KB server. + * @returns string of the KB server + * This function returns the url of the OCS knowledge base server. It´s possible to set it in the config file or it will fallback to the default + */ + private static function getKBURL(){ + $configurl=OC_Config::getValue('knowledgebaseurl', ''); + if($configurl<>'') { + $url=$configurl; + }else{ + $url='http://api.apps.owncloud.com/v1'; + } + return($url); + } + + /** * @brief Get all the categories from the OCS server * @returns array with category ids @@ -35,7 +67,7 @@ class OC_OCSClient{ * This function returns a list of all the application categories on the OCS server */ public static function getCategories(){ - $url='http://api.apps.owncloud.com/v1/content/categories'; + $url=OC_OCSClient::getAppStoreURL().'/content/categories'; $xml=@file_get_contents($url); if($xml==FALSE){ @@ -64,12 +96,16 @@ class OC_OCSClient{ * This function returns a list of all the applications on the OCS server */ public static function getApplications($categories){ + if(OC_Config::getValue('appstoreenabled', true)==false){ + return(array()); + } + if(is_array($categories)) { $categoriesstring=implode('x',$categories); }else{ $categoriesstring=$categories; } - $url='http://api.apps.owncloud.com/v1/content/data?categories='.urlencode($categoriesstring).'&sortmode=new&page=0&pagesize=10'; + $url=OC_OCSClient::getAppStoreURL().'/content/data?categories='.urlencode($categoriesstring).'&sortmode=new&page=0&pagesize=10'; $apps=array(); $xml=@file_get_contents($url); if($xml==FALSE){ @@ -104,7 +140,7 @@ class OC_OCSClient{ * This function returns an applications from the OCS server */ public static function getApplication($id){ - $url='http://api.apps.owncloud.com/v1/content/data/'.urlencode($id); + $url=OC_OCSClient::getAppStoreURL().'/content/data/'.urlencode($id); $xml=@file_get_contents($url); if($xml==FALSE){ @@ -137,7 +173,7 @@ class OC_OCSClient{ * This function returns an download url for an applications from the OCS server */ public static function getApplicationDownload($id,$item){ - $url='http://api.apps.owncloud.com/v1/content/download/'.urlencode($id).'/'.urlencode($item); + $url=OC_OCSClient::getAppStoreURL().'/content/download/'.urlencode($id).'/'.urlencode($item); $xml=@file_get_contents($url); if($xml==FALSE){ @@ -164,9 +200,15 @@ class OC_OCSClient{ * This function returns a list of all the knowledgebase entries from the OCS server */ public static function getKnownledgebaseEntries($page,$pagesize){ + if(OC_Config::getValue('knowledgebaseenabled', true)==false){ + $kbe=array(); + $kbe['totalitems']=0; + return $kbe; + } + $p= (int) $page; $s= (int) $pagesize; - $url='http://api.apps.owncloud.com/v1/knowledgebase/data?type=150&page='.$p.'&pagesize='.$s; + $url=OC_OCSClient::getKBURL().'/knowledgebase/data?type=150&page='.$p.'&pagesize='.$s; $kbe=array(); $xml=@file_get_contents($url); diff --git a/lib/remote/cloud.php b/lib/remote/cloud.php deleted file mode 100644 index a9c74e8bf5..0000000000 --- a/lib/remote/cloud.php +++ /dev/null @@ -1,204 +0,0 @@ -cookiefile){ - $this->cookiefile=get_temp_dir().'/remoteCloudCookie'.uniqid(); - } - $url=$this->path.='/files/api.php'; - $fields_string="action=$action&"; - if(is_array($parameters)){ - foreach($parameters as $key=>$value){ - $fields_string.=$key.'='.$value.'&'; - } - rtrim($fields_string,'&'); - } - $ch=curl_init(); - curl_setopt($ch,CURLOPT_URL,$url); - curl_setopt($ch,CURLOPT_POST,count($parameters)); - curl_setopt($ch,CURLOPT_POSTFIELDS,$fields_string); - curl_setopt($ch, CURLOPT_COOKIEFILE,$this->cookiefile); - curl_setopt($ch, CURLOPT_COOKIEJAR,$this->cookiefile); - curl_setopt($ch,CURLOPT_RETURNTRANSFER,true); - $result=curl_exec($ch); - $result=trim($result); - $info=curl_getinfo($ch); - $httpCode=$info['http_code']; - curl_close($ch); - if($httpCode==200 or $httpCode==0){ - return json_decode($result,$assoc); - }else{ - return false; - } - } - - public function __construct($path,$user,$password){ - $this->path=$path; - $this->connected=$this->apiCall('login',array('username'=>$user,'password'=>$password)); - } - - /** - * check if we are stull logged in on the remote cloud - * - */ - public function isLoggedIn(){ - if(!$this->connected){ - return false; - } - return $this->apiCall('checklogin'); - } - - public function __get($name){ - switch($name){ - case 'connected': - return $this->connected; - } - } - - /** - * disconnect from the remote cloud - * - */ - public function disconnect(){ - $this->connected=false; - if(is_file($this->cookiefile)){ - unlink($this->cookiefile); - } - $this->cookiefile=false; - } - - /** - * create a new file or directory - * @param string $dir - * @param string $name - * @param string $type - */ - public function newFile($dir,$name,$type){ - if(!$this->connected){ - return false; - } - return $this->apiCall('new',array('dir'=>$dir,'name'=>$name,'type'=>$type),true); - } - - /** - * deletes a file or directory - * @param string $dir - * @param string $file - */ - public function delete($dir,$name){ - if(!$this->connected){ - return false; - } - return $this->apiCall('delete',array('dir'=>$dir,'file'=>$name),true); - } - - /** - * moves a file or directory - * @param string $sorceDir - * @param string $sorceFile - * @param string $targetDir - * @param string $targetFile - */ - public function move($sourceDir,$sourceFile,$targetDir,$targetFile){ - if(!$this->connected){ - return false; - } - return $this->apiCall('move',array('sourcedir'=>$sourceDir,'source'=>$sourceFile,'targetdir'=>$targetDir,'target'=>$targetFile),true); - } - - /** - * copies a file or directory - * @param string $sorceDir - * @param string $sorceFile - * @param string $targetDir - * @param string $targetFile - */ - public function copy($sourceDir,$sourceFile,$targetDir,$targetFile){ - if(!$this->connected){ - return false; - } - return $this->apiCall('copy',array('sourcedir'=>$sourceDir,'source'=>$sourceFile,'targetdir'=>$targetDir,'target'=>$targetFile),true); - } - - /** - * get a file tree - * @param string $dir - */ - public function getTree($dir){ - if(!$this->connected){ - return false; - } - return $this->apiCall('gettree',array('dir'=>$dir),true); - } - - /** - * get the files inside a directory of the remote cloud - * @param string $dir - */ - public function getFiles($dir){ - if(!$this->connected){ - return false; - } - return $this->apiCall('getfiles',array('dir'=>$dir),true); - } - - /** - * get a remove file and save it in a temporary file and return the path of the temporary file - * @param string $dir - * @param string $file - * @return string - */ - public function getFile($dir, $file){ - if(!$this->connected){ - return false; - } - $ch=curl_init(); - if(!$this->cookiefile){ - $this->cookiefile=get_temp_dir().'/remoteCloudCookie'.uniqid(); - } - $tmpfile=tempnam(get_temp_dir(),'remoteCloudFile'); - $fp=fopen($tmpfile,'w+'); - $url=$this->path.="/files/api.php?action=get&dir=$dir&file=$file"; - curl_setopt($ch,CURLOPT_URL,$url); - curl_setopt($ch, CURLOPT_COOKIEFILE,$this->cookiefile); - curl_setopt($ch, CURLOPT_COOKIEJAR,$this->cookiefile); - curl_setopt($ch, CURLOPT_FILE, $fp); - curl_exec($ch); - fclose($fp); - curl_close($ch); - return $tmpfile; - } - - public function sendFile($sourceDir,$sourceFile,$targetDir,$targetFile){ - $source=$sourceDir.'/'.$sourceFile; - $tmp=OC_Filesystem::toTmpFile($source); - return $this->sendTmpFile($tmp,$targetDir,$targetFile); - } - - public function sendTmpFile($tmp,$targetDir,$targetFile){ - $token=sha1(uniqid().$tmp); - $file=get_temp_dir().'/'.'remoteCloudFile'.$token; - rename($tmp,$file); - if( OC_Config::getValue( "forcessl", false ) or isset($_SERVER['HTTPS']) and $_SERVER['HTTPS'] == 'on') { - $url = "https://". $_SERVER['SERVER_NAME'] . OC::$WEBROOT; - }else{ - $url = "http://". $_SERVER['SERVER_NAME'] . OC::$WEBROOT; - } - return $this->apiCall('pull',array('dir'=>$targetDir,'file'=>$targetFile,'token'=>$token,'source'=>$url),true); - } -} - diff --git a/lib/search.php b/lib/search.php index 6b33fa3814..1205541868 100644 --- a/lib/search.php +++ b/lib/search.php @@ -26,13 +26,22 @@ */ class OC_Search{ static private $providers=array(); + static private $registeredProviders=array(); + + /** + * remove all registered search providers + */ + public static function clearProviders(){ + self::$providers=array(); + self::$registeredProviders=array(); + } /** * register a new search provider to be used * @param string $provider class name of a OC_Search_Provider */ - public static function registerProvider($provider){ - self::$providers[]=$provider; + public static function registerProvider($class,$options=array()){ + self::$registeredProviders[]=array('class'=>$class,'options'=>$options); } /** @@ -41,10 +50,25 @@ class OC_Search{ * @return array An array of OC_Search_Result's */ public static function search($query){ + self::initProviders(); $results=array(); foreach(self::$providers as $provider){ - $results=array_merge($results, $provider::search($query)); + $results=array_merge($results, $provider->search($query)); } return $results; } + + /** + * create instances of all the registered search providers + */ + private static function initProviders(){ + if(count(self::$providers)>0){ + return; + } + foreach(self::$registeredProviders as $provider){ + $class=$provider['class']; + $options=$provider['options']; + self::$providers[]=new $class($options); + } + } } diff --git a/lib/search/provider.php b/lib/search/provider.php index 9487ca51f2..838ab696d0 100644 --- a/lib/search/provider.php +++ b/lib/search/provider.php @@ -2,11 +2,13 @@ /** * provides search functionalty */ -interface OC_Search_Provider { +class OC_Search_Provider { + public function __construct($options){} + /** * search for $query * @param string $query * @return array An array of OC_Search_Result's */ - static function search($query); + public function search($query){} } diff --git a/lib/search/provider/file.php b/lib/search/provider/file.php index 3bdb3bcd2a..a37af49559 100644 --- a/lib/search/provider/file.php +++ b/lib/search/provider/file.php @@ -1,7 +1,7 @@ renderas = $renderas; + $this->application = $app; + $this->vars = array(); + $this->l10n = OC_L10N::get($app); - // Read the detected formfactor and use the right file name. + $this->findTemplate($name); + } + + /** + * @brief Returns the formfactor extension for current formfactor + */ + protected function getFormFactorExtension() + { $formfactor=$_SESSION['formfactor']; if($formfactor=='default') { $fext=''; @@ -167,70 +177,79 @@ class OC_Template{ }else{ $fext=''; } + return $fext; + } + /** + * @brief find the template with the given name + * @param $name of the template file (without suffix) + * + * Will select the template file for the selected theme and formfactor. + * Checking all the possible locations. + */ + protected function findTemplate($name) + { + // Read the selected theme from the config file + $theme=OC_Config::getValue( "theme" ); + + // Read the detected formfactor and use the right file name. + $fext = $this->getFormFactorExtension(); + + $app = $this->application; // Check if it is a app template or not. if( $app != "" ){ // Check if the app is in the app folder or in the root if( file_exists( OC::$APPSROOT."/apps/$app/templates/" )){ // Check if the template is overwritten by the selected theme - if( file_exists( OC::$SERVERROOT."/themes/$theme/apps/$app/templates/"."$name$fext.php" )){ - $template = OC::$SERVERROOT."/themes/$theme/apps/$app/templates/"."$name$fext.php"; - $path = OC::$SERVERROOT."/themes/$theme/apps/$app/templates/"; - }elseif( file_exists( OC::$SERVERROOT."/themes/$theme/apps/$app/templates/"."$name.php" )){ - $template = OC::$SERVERROOT."/themes/$theme/apps/$app/templates/"."$name.php"; - $path = OC::$SERVERROOT."/themes/$theme/apps/$app/templates/"; - }elseif( OC::$APPSROOT."/apps/$app/templates/"."$name$fext.php" ){ - $template = OC::$APPSROOT."/apps/$app/templates/"."$name$fext.php"; - $path = OC::$APPSROOT."/apps/$app/templates/"; - }else{ - $template = OC::$APPSROOT."/apps/$app/templates/"."$name.php"; - $path = OC::$APPSROOT."/apps/$app/templates/"; + if ($this->checkPathForTemplate(OC::$SERVERROOT."/themes/$theme/apps/$app/templates/", $name, $fext)) { + }elseif ($this->checkPathForTemplate(OC::$APPSROOT."/apps/$app/templates/", $name, $fext)) { } }else{ // Check if the template is overwritten by the selected theme - if( file_exists( OC::$SERVERROOT."/themes/$theme/$app/templates/"."$name$fext.php" )){ - $template = OC::$SERVERROOT."/themes/$theme/$app/templates/"."$name$fext.php"; - $path = OC::$SERVERROOT."/themes/$theme/$app/templates/"; - }elseif( file_exists( OC::$SERVERROOT."/themes/$theme/$app/templates/"."$name.php" )){ - $template = OC::$SERVERROOT."/themes/$theme/$app/templates/"."$name.php"; - $path = OC::$SERVERROOT."/themes/$theme/$app/templates/"; - }elseif( file_exists( OC::$SERVERROOT."/$app/templates/"."$name$fext.php" )){ - $template = OC::$SERVERROOT."/$app/templates/"."$name$fext.php"; - $path = OC::$SERVERROOT."/$app/templates/"; - }elseif( file_exists( OC::$SERVERROOT."/$app/templates/"."$name.php" )){ - $template = OC::$SERVERROOT."/$app/templates/"."$name.php"; - $path = OC::$SERVERROOT."/$app/templates/"; + if ($this->checkPathForTemplate(OC::$SERVERROOT."/themes/$theme/$app/templates/", $name, $fext)) { + }elseif ($this->checkPathForTemplate(OC::$SERVERROOT."/$app/templates/", $name, $fext)) { }else{ - echo('template not found: template:'.$name.' formfactor:'.$fext.' webroot:'.OC::$WEBROOT.' serverroot:'.OC::$SERVERROOT); + echo('template not found: template:'.$name.' formfactor:'.$fext.' webroot:'.OC::$WEBROOT.' serverroot:'.OC::$SERVERROOT); die(); } - } + } }else{ // Check if the template is overwritten by the selected theme - if( file_exists( OC::$SERVERROOT."/themes/$theme/core/templates/"."$name$fext.php" )){ - $template = OC::$SERVERROOT."/themes/$theme/core/templates/"."$name$fext.php"; - $path = OC::$SERVERROOT."/themes/$theme/core/templates/"; - }elseif( file_exists( OC::$SERVERROOT."/themes/$theme/core/templates/"."$name.php" )){ - $template = OC::$SERVERROOT."/themes/$theme/core/templates/"."$name.php"; - $path = OC::$SERVERROOT."/themes/$theme/core/templates/"; - }elseif( file_exists( OC::$SERVERROOT."/core/templates/"."$name$fext.php" )){ - $template = OC::$SERVERROOT."/core/templates/"."$name$fext.php"; - $path = OC::$SERVERROOT."/core/templates/"; + if ($this->checkPathForTemplate(OC::$SERVERROOT."/themes/$theme/core/templates/", $name, $fext)) { + } elseif ($this->checkPathForTemplate(OC::$SERVERROOT."/core/templates/", $name, $fext)) { }else{ - $template = OC::$SERVERROOT."/core/templates/"."$name.php"; - $path = OC::$SERVERROOT."/core/templates/"; + echo('template not found: template:'.$name.' formfactor:'.$fext.' webroot:'.OC::$WEBROOT.' serverroot:'.OC::$SERVERROOT); + die(); } } + } - - // Set the private data - $this->renderas = $renderas; - $this->application = $app; - $this->template = $template; - $this->path = $path; - $this->vars = array(); - $this->l10n = new OC_L10N($app); + /** + * @brief check Path For Template with and without $fext + * @param $path to check + * @param $name of the template file (without suffix) + * @param $fext formfactor extension + * @return bool true when found + * + * Will set $this->template and $this->path if there is a template at + * the specifief $path + */ + protected function checkPathForTemplate($path, $name, $fext) + { + if ($name =='') return false; + $template = null; + if( is_file( $path.$name.$fext.'.php' )){ + $template = $path.$name.$fext.'.php'; + }elseif( is_file( $path.$name.'.php' )){ + $template = $path.$name.'.php'; + } + if ($template) { + $this->template = $template; + $this->path = $path; + return true; + } + return false; } /** @@ -267,7 +286,7 @@ class OC_Template{ $this->vars[$key] = array( $value ); } } - + /** * @brief Add a custom element to the header * @param string tag tag name of the element @@ -295,11 +314,25 @@ class OC_Template{ } } + /* + * @brief append the $file-url if exist at $root + * @param $type of collection to use when appending + * @param $root path to check + * @param $web base for path + * @param $file the filename + */ + public function appendIfExist($type, $root, $web, $file) { + if (is_file($root.'/'.$file)) { + $this->append( $type, $web.'/'.$file); + return true; + } + return false; + } /** * @brief Proceeds the template * @returns content * - * This function proceeds the template. If $this->renderas is set, it will + * This function proceeds the template. If $this->renderas is set, it * will produce a full page. */ public function fetchPage(){ @@ -329,68 +362,44 @@ class OC_Template{ }else{ $page = new OC_Template( "core", "layout.guest" ); } - + // Read the selected theme from the config file $theme=OC_Config::getValue( "theme" ); // Read the detected formfactor and use the right file name. - $formfactor=$_SESSION['formfactor']; - if($formfactor=='default') { - $fext=''; - }elseif($formfactor=='mobile') { - $fext='.mobile'; - }elseif($formfactor=='tablet') { - $fext='.tablet'; - }elseif($formfactor=='standalone') { - $fext='.standalone'; - }else{ - $fext=''; - } + $fext = $this->getFormFactorExtension(); // Add the core js files or the js files provided by the selected theme foreach(OC_Util::$scripts as $script){ // Is it in 3rd party? - if(is_file(OC::$THIRDPARTYROOT."/$script.js" )){ - $page->append( "jsfiles", OC::$THIRDPARTYWEBROOT."/$script.js" ); + if($page->appendIfExist('jsfiles', OC::$THIRDPARTYROOT, OC::$THIRDPARTYWEBROOT, $script.'.js')) { // Is it in apps and overwritten by the theme? - }elseif(is_file(OC::$SERVERROOT."/themes/$theme/apps/$script$fext.js" )){ - $page->append( "jsfiles", OC::$WEBROOT."/themes/$theme/apps/$script$fext.js" ); - }elseif(is_file(OC::$SERVERROOT."/themes/$theme/apps/$script.js" )){ - $page->append( "jsfiles", OC::$WEBROOT."/themes/$theme/apps/$script.js" ); + }elseif($page->appendIfExist('jsfiles', OC::$SERVERROOT, OC::$WEBROOT, "themes/$theme/apps/$script$fext.js" )) { + }elseif($page->appendIfExist('jsfiles', OC::$SERVERROOT, OC::$WEBROOT, "themes/$theme/apps/$script.js" )) { // Is it part of an app? - }elseif(is_file(OC::$APPSROOT."/apps/$script$fext.js" )){ - $page->append( "jsfiles", OC::$APPSWEBROOT."/apps/$script$fext.js" ); - }elseif(is_file(OC::$APPSROOT."/apps/$script.js" )){ - $page->append( "jsfiles", OC::$APPSWEBROOT."/apps/$script.js" ); + }elseif($page->appendIfExist('jsfiles', OC::$APPSROOT, OC::$APPSWEBROOT, "apps/$script$fext.js" )) { + }elseif($page->appendIfExist('jsfiles', OC::$APPSROOT, OC::$APPSWEBROOT, "apps/$script.js" )) { // Is it in the owncloud root but overwritten by the theme? - }elseif(is_file(OC::$SERVERROOT."/themes/$theme/$script$fext.js" )){ - $page->append( "jsfiles", OC::$WEBROOT."/themes/$theme/$script$fext.js" ); - }elseif(is_file(OC::$SERVERROOT."/themes/$theme/$script.js" )){ - $page->append( "jsfiles", OC::$WEBROOT."/themes/$theme/$script.js" ); - + }elseif($page->appendIfExist('jsfiles', OC::$SERVERROOT, OC::$WEBROOT, "themes/$theme/$script$fext.js" )) { + }elseif($page->appendIfExist('jsfiles', OC::$SERVERROOT, OC::$WEBROOT, "themes/$theme/$script.js" )) { + // Is it in the owncloud root ? - }elseif(is_file(OC::$SERVERROOT."/$script$fext.js" )){ - $page->append( "jsfiles", OC::$WEBROOT."/$script$fext.js" ); - }elseif(is_file(OC::$SERVERROOT."/$script.js" )){ - $page->append( "jsfiles", OC::$WEBROOT."/$script.js" ); + }elseif($page->appendIfExist('jsfiles', OC::$SERVERROOT, OC::$WEBROOT, "$script$fext.js" )) { + }elseif($page->appendIfExist('jsfiles', OC::$SERVERROOT, OC::$WEBROOT, "$script.js" )) { // Is in core but overwritten by a theme? - }elseif(is_file(OC::$SERVERROOT."/themes/$theme/core/$script$fext.js" )){ - $page->append( "jsfiles", OC::$WEBROOT."/themes/$theme/core/$script$fext.js" ); - }elseif(is_file(OC::$SERVERROOT."/themes/$theme/core/$script.js" )){ - $page->append( "jsfiles", OC::$WEBROOT."/themes/$theme/core/$script.js" ); + }elseif($page->appendIfExist('jsfiles', OC::$SERVERROOT, OC::$WEBROOT, "themes/$theme/core/$script$fext.js" )) { + }elseif($page->appendIfExist('jsfiles', OC::$SERVERROOT, OC::$WEBROOT, "themes/$theme/core/$script.js" )) { // Is it in core? - }elseif(is_file(OC::$SERVERROOT."/core/$script$fext.js" )){ - $page->append( "jsfiles", OC::$WEBROOT."/core/$script$fext.js" ); - }elseif(is_file(OC::$SERVERROOT."/core/$script.js" )){ - $page->append( "jsfiles", OC::$WEBROOT."/core/$script.js" ); + }elseif($page->appendIfExist('jsfiles', OC::$SERVERROOT, OC::$WEBROOT, "core/$script$fext.js" )) { + }elseif($page->appendIfExist('jsfiles', OC::$SERVERROOT, OC::$WEBROOT, "core/$script.js" )) { }else{ - echo('js file not found: script:'.$script.' formfactor:'.$fext.' webroot:'.OC::$WEBROOT.' serverroot:'.OC::$SERVERROOT); + echo('js file not found: script:'.$script.' formfactor:'.$fext.' webroot:'.OC::$WEBROOT.' serverroot:'.OC::$SERVERROOT); die(); } @@ -398,54 +407,46 @@ class OC_Template{ // Add the css files foreach(OC_Util::$styles as $style){ // is it in 3rdparty? - if(is_file(OC::$THIRDPARTYROOT."/$style.css" )){ - $page->append( "cssfiles", OC::$THIRDPARTYWEBROOT."/$style.css" ); + if($page->appendIfExist('cssfiles', OC::$THIRDPARTYROOT, OC::$THIRDPARTYWEBROOT, $style.'.css')) { + // or in apps? - }elseif(is_file(OC::$APPSROOT."/apps/$style$fext.css" )){ - $page->append( "cssfiles", OC::$APPSWEBROOT."/apps/$style$fext.css" ); - }elseif(is_file(OC::$APPSROOT."/apps/$style.css" )){ - $page->append( "cssfiles", OC::$APPSWEBROOT."/apps/$style.css" ); + }elseif($page->appendIfExist('cssfiles', OC::$APPSROOT, OC::$APPSWEBROOT, "apps/$style$fext.css" )) { + }elseif($page->appendIfExist('cssfiles', OC::$APPSROOT, OC::$APPSWEBROOT, "apps/$style.css" )) { + // or in the owncloud root? - }elseif(is_file(OC::$SERVERROOT."/$style$fext.css" )){ - $page->append( "cssfiles", OC::$WEBROOT."/$style$fext.css" ); - }elseif(is_file(OC::$SERVERROOT."/$style.css" )){ - $page->append( "cssfiles", OC::$WEBROOT."/$style.css" ); - // or in core ? - }elseif(is_file(OC::$SERVERROOT."/core/$style$fext.css" )){ - $page->append( "cssfiles", OC::$WEBROOT."/core/$style$fext.css" ); - }elseif(is_file(OC::$SERVERROOT."/core/$style.css" )){ - $page->append( "cssfiles", OC::$WEBROOT."/core/$style.css" ); + }elseif($page->appendIfExist('cssfiles', OC::$SERVERROOT, OC::$WEBROOT, "$style$fext.css" )) { + }elseif($page->appendIfExist('cssfiles', OC::$SERVERROOT, OC::$WEBROOT, "$style.css" )) { + + // or in core ? + }elseif($page->appendIfExist('cssfiles', OC::$SERVERROOT, OC::$WEBROOT, "core/$style$fext.css" )) { + }elseif($page->appendIfExist('cssfiles', OC::$SERVERROOT, OC::$WEBROOT, "core/$style.css" )) { }else{ - echo('css file not found: style:'.$script.' formfactor:'.$fext.' webroot:'.OC::$WEBROOT.' serverroot:'.OC::$SERVERROOT); + echo('css file not found: style:'.$script.' formfactor:'.$fext.' webroot:'.OC::$WEBROOT.' serverroot:'.OC::$SERVERROOT); die(); } } - // Add the theme css files. you can override the default values here + // Add the theme css files. you can override the default values here if(!empty($theme)) { - foreach(OC_Util::$styles as $style){ - if(is_file(OC::$SERVERROOT."/themes/$theme/apps/$style$fext.css" )){ - $page->append( "cssfiles", OC::$WEBROOT."/themes/$theme/apps/$style$fext.css" ); - }elseif(is_file(OC::$SERVERROOT."/themes/$theme/apps/$style.css" )){ - $page->append( "cssfiles", OC::$WEBROOT."/themes/$theme/apps/$style.css" ); - }elseif(is_file(OC::$SERVERROOT."/themes/$theme/$style$fext.css" )){ - $page->append( "cssfiles", OC::$WEBROOT."/themes/$theme/$style$fext.css" ); - }elseif(is_file(OC::$SERVERROOT."/themes/$theme/$style.css" )){ - $page->append( "cssfiles", OC::$WEBROOT."/themes/$theme/$style.css" ); - }elseif(is_file(OC::$SERVERROOT."/themes/$theme/core/$style$fext.css" )){ - $page->append( "cssfiles", OC::$WEBROOT."/themes/$theme/core/$style$fext.css" ); - }elseif(is_file(OC::$SERVERROOT."/themes/$theme/core/$style.css" )){ - $page->append( "cssfiles", OC::$WEBROOT."/themes/$theme/core/$style.css" ); - } - } - } - + foreach(OC_Util::$styles as $style){ + if($page->appendIfExist('cssfiles', OC::$SERVERROOT, OC::$WEBROOT, "themes/$theme/apps/$style$fext.css" )) { + }elseif($page->appendIfExist('cssfiles', OC::$SERVERROOT, OC::$WEBROOT, "themes/$theme/apps/$style.css" )) { + + }elseif($page->appendIfExist('cssfiles', OC::$SERVERROOT, OC::$WEBROOT, "themes/$theme/$style$fext.css" )) { + }elseif($page->appendIfExist('cssfiles', OC::$SERVERROOT, OC::$WEBROOT, "themes/$theme/$style.css" )) { + + }elseif($page->appendIfExist('cssfiles', OC::$SERVERROOT, OC::$WEBROOT, "themes/$theme/core/$style$fext.css" )) { + }elseif($page->appendIfExist('cssfiles', OC::$SERVERROOT, OC::$WEBROOT, "themes/$theme/core/$style.css" )) { + } + } + } + // Add custom headers $page->assign('headers',$this->headers); foreach(OC_Util::$headers as $header){ $page->append('headers',$header); } - + // Add css files and js files $page->assign( "content", $data ); return $page->fetchPage(); diff --git a/lib/updater.php b/lib/updater.php index 57623797ae..deb0f05945 100644 --- a/lib/updater.php +++ b/lib/updater.php @@ -36,6 +36,7 @@ class OC_Updater{ $version['installed']=OC_Config::getValue('installedat'); $version['updated']=OC_Appconfig::getValue('core', 'lastupdatedat', OC_Config::getValue( 'lastupdatedat')); $version['updatechannel']='stable'; + $version['edition']=OC_Util::getEditionString(); $versionstring=implode('x',$version); //fetch xml data from updater @@ -58,9 +59,9 @@ class OC_Updater{ public static function ShowUpdatingHint(){ $data=OC_Updater::check(); if(isset($data['version']) and $data['version']<>'') { - $txt=''.$data['versionstring'].' is available. Please click here for more information'; + $txt=''.$data['versionstring'].' is available. Get more information'; }else{ - $txt='Your ownCloud is up to date'; + $txt='up to date'; } return($txt); } diff --git a/lib/user.php b/lib/user.php index fda19a3315..8c27ec30cc 100644 --- a/lib/user.php +++ b/lib/user.php @@ -186,7 +186,7 @@ class OC_User { * @param $password The password of the user * @returns true/false * - * Log in a user - if the password is ok + * Log in a user and regenerate a new session - if the password is ok */ public static function login( $uid, $password ){ $run = true; @@ -195,6 +195,7 @@ class OC_User { if( $run ){ $uid=self::checkPassword( $uid, $password ); if($uid){ + session_regenerate_id(); self::setUserId($uid); OC_Hook::emit( "OC_User", "post_login", array( "uid" => $uid, 'password'=>$password )); return true; @@ -221,7 +222,8 @@ class OC_User { */ public static function logout(){ OC_Hook::emit( "OC_User", "logout", array()); - $_SESSION['user_id'] = false; + session_unset(); + session_destroy(); OC_User::unsetMagicInCookie(); return true; } diff --git a/lib/user/database.php b/lib/user/database.php index 3eade276dd..c1bac1bb0b 100644 --- a/lib/user/database.php +++ b/lib/user/database.php @@ -172,7 +172,7 @@ class OC_User_Database extends OC_User_Backend { * @return boolean */ public function userExists($uid){ - $query = OC_DB::prepare( "SELECT * FROM `*PREFIX*users` WHERE uid = ?" ); + $query = OC_DB::prepare( "SELECT * FROM `*PREFIX*users` WHERE uid LIKE ?" ); $result = $query->execute( array( $uid )); return $result->numRows() > 0; diff --git a/lib/user/dummy.php b/lib/user/dummy.php new file mode 100644 index 0000000000..cfc96c5c52 --- /dev/null +++ b/lib/user/dummy.php @@ -0,0 +1,114 @@ +. +* +*/ + +/** + * dummy user backend, does not keep state, only for testing use + */ +class OC_User_Dummy extends OC_User_Backend { + private $users=array(); + /** + * @brief Create a new user + * @param $uid The username of the user to create + * @param $password The password of the new user + * @returns true/false + * + * Creates a new user. Basic checking of username is done in OC_User + * itself, not in its subclasses. + */ + public function createUser($uid, $password){ + if(isset($this->users[$uid])){ + return false; + }else{ + $this->users[$uid]=$password; + return true; + } + } + + /** + * @brief delete a user + * @param $uid The username of the user to delete + * @returns true/false + * + * Deletes a user + */ + public function deleteUser( $uid ){ + if(isset($this->users[$uid])){ + unset($this->users[$uid]); + return true; + }else{ + return false; + } + } + + /** + * @brief Set password + * @param $uid The username + * @param $password The new password + * @returns true/false + * + * Change the password of a user + */ + public function setPassword($uid, $password){ + if(isset($this->users[$uid])){ + $this->users[$uid]=$password; + return true; + }else{ + return false; + } + } + + /** + * @brief Check if the password is correct + * @param $uid The username + * @param $password The password + * @returns true/false + * + * Check if the password is correct without logging in the user + */ + public function checkPassword($uid, $password){ + if(isset($this->users[$uid])){ + return ($this->users[$uid]==$password); + }else{ + return false; + } + } + + /** + * @brief Get a list of all users + * @returns array with all uids + * + * Get a list of all users. + */ + public function getUsers(){ + return array_keys($this->users); + } + + /** + * @brief check if a user exists + * @param string $uid the username + * @return boolean + */ + public function userExists($uid){ + return isset($this->users[$uid]); + } +} diff --git a/lib/util.php b/lib/util.php index fa5b3daaab..2ea392ec31 100644 --- a/lib/util.php +++ b/lib/util.php @@ -25,7 +25,7 @@ class OC_Util { $success=@mkdir($CONFIG_DATADIRECTORY_ROOT); if(!$success) { $tmpl = new OC_Template( '', 'error', 'guest' ); - $tmpl->assign('errors',array(1=>array('error'=>"Can't create data directory (".$CONFIG_DATADIRECTORY_ROOT.")",'hint'=>"You can usually fix this by giving the webserver write access to the ownCloud directory '".OC::$SERVERROOT."' "))); + $tmpl->assign('errors',array(1=>array('error'=>"Can't create data directory (".$CONFIG_DATADIRECTORY_ROOT.")",'hint'=>"You can usually fix this by giving the webserver write access to the ownCloud directory '".OC::$SERVERROOT."' (in a terminal, use the command 'chown -R www-data:www-data /path/to/your/owncloud/install/data' "))); $tmpl->printPage(); exit; } @@ -66,7 +66,7 @@ class OC_Util { * @return array */ public static function getVersion(){ - return array(3,00,3); + return array(3,80,0); } /** @@ -74,9 +74,17 @@ class OC_Util { * @return string */ public static function getVersionString(){ - return '3'; + return '4 alpha'; } + /** + * get the current installed edition of ownCloud. There is the community edition that just returns an empty string and the enterprise edition that returns "Enterprise". + * @return string + */ + public static function getEditionString(){ + return ''; + } + /** * add a javascript file * diff --git a/lib/vcategories.php b/lib/vcategories.php index 5a7bacd202..b3b6a493c8 100644 --- a/lib/vcategories.php +++ b/lib/vcategories.php @@ -50,13 +50,12 @@ class OC_VCategories { * parameter should normally be omitted but to make an app able to * update categories for all users it is made possible to provide it. * @param $defcategories An array of default categories to be used if none is stored. - * NOTE: Not implemented. */ - public function __construct($app, $user=null, $defcategories=null) { + public function __construct($app, $user=null, $defcategories=array()) { $this->app = $app; $this->user = is_null($user) ? OC_User::getUser() : $user; $categories = trim(OC_Preferences::getValue($this->user, $app, self::PREF_CATEGORIES_LABEL, '')); - $this->categories = $categories != '' ? unserialize($categories) : array(); + $this->categories = $categories != '' ? unserialize($categories) : $defcategories; } /** @@ -65,6 +64,7 @@ class OC_VCategories { */ public function categories() { OC_Log::write('core','OC_VCategories::categories: '.print_r($this->categories, true), OC_Log::DEBUG); + usort($this->categories, 'strnatcasecmp'); // usort to also renumber the keys return $this->categories; } @@ -97,7 +97,6 @@ class OC_VCategories { } if(count($newones) > 0) { $this->categories = array_merge($this->categories, $newones); - natcasesort($this->categories); // Dunno if this is necessary if($sync === true) { $this->save(); } @@ -147,6 +146,7 @@ class OC_VCategories { * @brief Save the list with categories */ private function save() { + usort($this->categories, 'strnatcasecmp'); // usort to also renumber the keys $escaped_categories = serialize($this->categories); OC_Log::write('core','OC_VCategories::save: '.print_r($this->categories, true), OC_Log::DEBUG); OC_Preferences::setValue($this->user, $this->app, self::PREF_CATEGORIES_LABEL, $escaped_categories); diff --git a/settings/ajax/disableapp.php b/settings/ajax/disableapp.php index 06dd3c2ac6..53e9be379e 100644 --- a/settings/ajax/disableapp.php +++ b/settings/ajax/disableapp.php @@ -6,4 +6,4 @@ OC_JSON::setContentTypeHeader(); OC_App::disable($_POST['appid']); -?> +OC_JSON::success(); diff --git a/settings/ajax/enableapp.php b/settings/ajax/enableapp.php index 639df2aecc..cb116ebe4e 100644 --- a/settings/ajax/enableapp.php +++ b/settings/ajax/enableapp.php @@ -5,6 +5,8 @@ require_once('../../lib/base.php'); OC_JSON::checkAdminUser(); OC_JSON::setContentTypeHeader(); -OC_App::enable($_POST['appid']); - -?> +if(OC_App::enable($_POST['appid'])){ + OC_JSON::success(); +}else{ + OC_JSON::error(); +} diff --git a/settings/ajax/getlog.php b/settings/ajax/getlog.php index 600ebefcec..ed48b2cae1 100644 --- a/settings/ajax/getlog.php +++ b/settings/ajax/getlog.php @@ -13,5 +13,5 @@ OC_JSON::checkAdminUser(); $count=(isset($_GET['count']))?$_GET['count']:50; $offset=(isset($_GET['offset']))?$_GET['offset']:0; -$entries=OC_Log::getEntries($count,$offset); +$entries=OC_Log_Owncloud::getEntries($count,$offset); OC_JSON::success(array("data" => $entries)); diff --git a/settings/ajax/lostpassword.php b/settings/ajax/lostpassword.php index a2dfc03320..5874dec964 100644 --- a/settings/ajax/lostpassword.php +++ b/settings/ajax/lostpassword.php @@ -5,7 +5,7 @@ require_once('../../lib/base.php'); OC_JSON::checkLoggedIn(); -$l=new OC_L10N('core'); +$l=OC_L10N::get('core'); // Get data if( isset( $_POST['email'] ) ){ diff --git a/settings/ajax/openid.php b/settings/ajax/openid.php index c4b119b448..58d071255c 100644 --- a/settings/ajax/openid.php +++ b/settings/ajax/openid.php @@ -3,7 +3,7 @@ // Init owncloud require_once('../../lib/base.php'); -$l=new OC_L10N('settings'); +$l=OC_L10N::get('settings'); OC_JSON::checkLoggedIn(); OC_JSON::checkAppEnabled('user_openid'); diff --git a/settings/ajax/setlanguage.php b/settings/ajax/setlanguage.php index dc1128de2e..d8d1a86037 100644 --- a/settings/ajax/setlanguage.php +++ b/settings/ajax/setlanguage.php @@ -3,7 +3,7 @@ // Init owncloud require_once('../../lib/base.php'); -$l=new OC_L10N('settings'); +$l=OC_L10N::get('settings'); OC_JSON::checkLoggedIn(); diff --git a/settings/css/settings.css b/settings/css/settings.css index e80de0f1ad..42576953d0 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -5,6 +5,7 @@ input#openid, input#webdav { width:20em; } #passworderror { display:none; } #passwordchanged { display:none; } input#identity { width:20em; } +#email { width: 17em; } .msg.success{ color:#fff; background-color:#0f0; padding:3px; text-shadow:1px 1px #000; } .msg.error{ color:#fff; background-color:#f00; padding:3px; text-shadow:1px 1px #000; } diff --git a/settings/js/apps.js b/settings/js/apps.js index e2f882c6fe..12d09ac69d 100644 --- a/settings/js/apps.js +++ b/settings/js/apps.js @@ -28,10 +28,18 @@ $(document).ready(function(){ var active=$(this).data('active'); if(app){ if(active){ - $.post(OC.filePath('settings','ajax','disableapp.php'),{appid:app}); + $.post(OC.filePath('settings','ajax','disableapp.php'),{appid:app},function(result){ + if(!result || result.status!='success'){ + OC.dialogs.alert('Error','Error while disabling app'); + } + },'json'); $('#leftcontent li[data-id="'+app+'"]').removeClass('active'); }else{ - $.post(OC.filePath('settings','ajax','enableapp.php'),{appid:app}); + $.post(OC.filePath('settings','ajax','enableapp.php'),{appid:app},function(result){ + if(!result || result.status!='success'){ + OC.dialogs.alert('Error','Error while enabling app'); + } + },'json'); $('#leftcontent li[data-id="'+app+'"]').addClass('active'); } active=!active; diff --git a/settings/js/log.js b/settings/js/log.js index 3814d9c10b..ae83f0a628 100644 --- a/settings/js/log.js +++ b/settings/js/log.js @@ -32,7 +32,7 @@ OC.Log={ row.append(messageTd); var timeTd=$('
    '); - timeTd.text(formatDate(entry.time)); + timeTd.text(formatDate(entry.time*1000)); row.append(timeTd); $('#log').append(row); } diff --git a/settings/log.php b/settings/log.php index 946f2b6f8e..ddbf72c443 100644 --- a/settings/log.php +++ b/settings/log.php @@ -28,7 +28,7 @@ OC_Util::addStyle( "settings", "settings" ); OC_Util::addScript( "settings", "apps" ); OC_App::setActiveNavigationEntry( "core_log" ); -$entries=OC_Log::getEntries(); +$entries=OC_Log_Owncloud::getEntries(); OC_Util::addScript('settings','log'); OC_Util::addStyle('settings','settings'); diff --git a/settings/personal.php b/settings/personal.php index 07030109de..41499657ac 100755 --- a/settings/personal.php +++ b/settings/personal.php @@ -35,7 +35,7 @@ array_unshift($languageCodes,$lang); $languageNames=include 'languageCodes.php'; $languages=array(); foreach($languageCodes as $lang){ - $l=new OC_L10N('settings',$lang); + $l=OC_L10N::get('settings',$lang); if(substr($l->t('__language_name__'),0,1)!='_'){//first check if the language name is in the translation file $languages[]=array('code'=>$lang,'name'=>$l->t('__language_name__')); }elseif(isset($languageNames[$lang])){ diff --git a/settings/templates/apps.php b/settings/templates/apps.php index 27133e9e67..1e49b4c892 100644 --- a/settings/templates/apps.php +++ b/settings/templates/apps.php @@ -5,7 +5,7 @@ */?>
      diff --git a/settings/templates/personal.php b/settings/templates/personal.php index 57731d979d..986cfe55ae 100644 --- a/settings/templates/personal.php +++ b/settings/templates/personal.php @@ -50,10 +50,8 @@ };?>

      - ownCloud
      - developed by the ownCloud community
      -
      - source code licensed freely under AGPL + ownCloud ()
      + Developed by the ownCloud community, the source code is freely licensed under the AGPL.

      diff --git a/status.php b/status.php index 94c8cfce84..81f339fa53 100644 --- a/status.php +++ b/status.php @@ -26,7 +26,7 @@ $RUNTIME_NOAPPS = TRUE; //no apps, yet require_once('lib/base.php'); if(OC_Config::getValue('installed')==1) $installed='true'; else $installed='false'; -$values=array('installed'=>$installed,'version'=>implode('.',OC_Util::getVersion()),'versionstring'=>OC_Util::getVersionString()); +$values=array('installed'=>$installed,'version'=>implode('.',OC_Util::getVersion()),'versionstring'=>OC_Util::getVersionString(),'edition'=>OC_Util::getEditionString()); echo(json_encode($values)); diff --git a/tests/index.php b/tests/index.php index 2e86366740..a6f678b3bc 100644 --- a/tests/index.php +++ b/tests/index.php @@ -38,6 +38,7 @@ foreach($apps as $app){ } function loadTests($dir=''){ + $test=isset($_GET['test'])?$_GET['test']:false; if($dh=opendir($dir)){ while($name=readdir($dh)){ if(substr($name,0,1)!='.'){//no hidden files, '.' or '..' @@ -45,10 +46,13 @@ function loadTests($dir=''){ if(is_dir($file)){ loadTests($file); }elseif(substr($file,-4)=='.php' and $file!=__FILE__){ - $testCase=new TestSuite(getTestName($file)); - $testCase->addFile($file); - if($testCase->getSize()>0){ - $testCase->run(new HtmlReporter()); + $name=getTestName($file); + if($test===false or $test==$name or substr($name,0,strlen($test))==$test){ + $testCase=new TestSuite($name); + $testCase->addFile($file); + if($testCase->getSize()>0){ + $testCase->run(new HtmlReporter()); + } } } } diff --git a/tests/lib/filestorage.php b/tests/lib/filestorage.php index 9ffa0eca9c..4858234a2d 100644 --- a/tests/lib/filestorage.php +++ b/tests/lib/filestorage.php @@ -135,10 +135,12 @@ abstract class Test_FileStorage extends UnitTestCase { $ctimeEnd=time(); $cTime=$this->instance->filectime('/lorem.txt'); $mTime=$this->instance->filemtime('/lorem.txt'); - $this->assertTrue($ctimeStart<=$cTime); - $this->assertTrue($cTime<=$ctimeEnd); - $this->assertTrue($ctimeStart<=$mTime); - $this->assertTrue($mTime<=$ctimeEnd); + if($cTime!=-1){//not everything can support ctime + $this->assertTrue(($ctimeStart-1)<=$cTime); + $this->assertTrue($cTime<=($ctimeEnd+1)); + } + $this->assertTrue(($ctimeStart-1)<=$mTime); + $this->assertTrue($mTime<=($ctimeEnd+1)); $this->assertEqual(filesize($textFile),$this->instance->filesize('/lorem.txt')); $stat=$this->instance->stat('/lorem.txt'); @@ -153,8 +155,8 @@ abstract class Test_FileStorage extends UnitTestCase { $originalCTime=$cTime; $cTime=$this->instance->filectime('/lorem.txt'); $mTime=$this->instance->filemtime('/lorem.txt'); - $this->assertTrue($mtimeStart<=$mTime); - $this->assertTrue($mTime<=$mtimeEnd); + $this->assertTrue(($mtimeStart-1)<=$mTime); + $this->assertTrue($mTime<=($mtimeEnd+1)); $this->assertEqual($cTime,$originalCTime); if($this->instance->touch('/lorem.txt',100)!==false){ @@ -170,8 +172,8 @@ abstract class Test_FileStorage extends UnitTestCase { $mtimeEnd=time(); $originalCTime=$cTime; $mTime=$this->instance->filemtime('/lorem.txt'); - $this->assertTrue($mtimeStart<=$mTime); - $this->assertTrue($mTime<=$mtimeEnd); + $this->assertTrue(($mtimeStart-1)<=$mTime); + $this->assertTrue($mTime<=($mtimeEnd+1)); } public function testSearch(){ diff --git a/tests/lib/filestorage/local.php b/tests/lib/filestorage/local.php index 0a2d46c5eb..692f05f9fc 100644 --- a/tests/lib/filestorage/local.php +++ b/tests/lib/filestorage/local.php @@ -26,10 +26,7 @@ class Test_Filestorage_Local extends Test_FileStorage { */ private $tmpDir; public function setUp(){ - $this->tmpDir=get_temp_dir().'/filestoragelocal'; - if(!file_exists($this->tmpDir)){ - mkdir($this->tmpDir); - } + $this->tmpDir=OC_Helper::tmpFolder(); $this->instance=new OC_Filestorage_Local(array('datadir'=>$this->tmpDir)); } diff --git a/tests/lib/filesystem.php b/tests/lib/filesystem.php new file mode 100644 index 0000000000..3e28d8c06e --- /dev/null +++ b/tests/lib/filesystem.php @@ -0,0 +1,64 @@ +. +* +*/ + +class Test_Filesystem extends UnitTestCase{ + /** + * @var array tmpDirs + */ + private $tmpDirs; + + /** + * @return array + */ + private function getStorageData(){ + $dir=OC_Helper::tmpFolder(); + $this->tmpDirs[]=$dir; + return array('datadir'=>$dir); + } + + public function tearDown(){ + foreach($this->tmpDirs as $dir){ + OC_Helper::rmdirr($dir); + } + } + + public function setUp(){ + OC_Filesystem::clearMounts(); + } + + public function testMount(){ + OC_Filesystem::mount('OC_Filestorage_Local',self::getStorageData(),'/'); + $this->assertEqual('/',OC_Filesystem::getMountPoint('/')); + $this->assertEqual('/',OC_Filesystem::getMountPoint('/some/folder')); + $this->assertEqual('',OC_Filesystem::getInternalPath('/')); + $this->assertEqual('some/folder',OC_Filesystem::getInternalPath('/some/folder')); + + OC_Filesystem::mount('OC_Filestorage_Local',self::getStorageData(),'/some'); + $this->assertEqual('/',OC_Filesystem::getMountPoint('/')); + $this->assertEqual('/some/',OC_Filesystem::getMountPoint('/some/folder')); + $this->assertEqual('/some/',OC_Filesystem::getMountPoint('/some/')); + $this->assertEqual('/',OC_Filesystem::getMountPoint('/some')); + $this->assertEqual('folder',OC_Filesystem::getInternalPath('/some/folder')); + } +} + +?> \ No newline at end of file diff --git a/tests/lib/group.php b/tests/lib/group.php new file mode 100644 index 0000000000..922613211b --- /dev/null +++ b/tests/lib/group.php @@ -0,0 +1,114 @@ +. +* +*/ + +class Test_Group extends UnitTestCase { + function setUp(){ + OC_Group::clearBackends(); + } + + function testSingleBackend(){ + OC_Group::useBackend(new OC_Group_Dummy()); + + $group1=uniqid(); + $group2=uniqid(); + OC_Group::createGroup($group1); + OC_Group::createGroup($group2); + + $user1=uniqid(); + $user2=uniqid(); + + $this->assertFalse(OC_Group::inGroup($user1,$group1)); + $this->assertFalse(OC_Group::inGroup($user2,$group1)); + $this->assertFalse(OC_Group::inGroup($user1,$group2)); + $this->assertFalse(OC_Group::inGroup($user2,$group2)); + + $this->assertTrue(OC_Group::addToGroup($user1,$group1)); + + $this->assertTrue(OC_Group::inGroup($user1,$group1)); + $this->assertFalse(OC_Group::inGroup($user2,$group1)); + $this->assertFalse(OC_Group::inGroup($user1,$group2)); + $this->assertFalse(OC_Group::inGroup($user2,$group2)); + + $this->assertFalse(OC_Group::addToGroup($user1,$group1)); + + $this->assertEqual(array($user1),OC_Group::usersInGroup($group1)); + $this->assertEqual(array(),OC_Group::usersInGroup($group2)); + + $this->assertEqual(array($group1),OC_Group::getUserGroups($user1)); + $this->assertEqual(array(),OC_Group::getUserGroups($user2)); + + OC_Group::deleteGroup($group1); + $this->assertEqual(array(),OC_Group::getUserGroups($user1)); + $this->assertEqual(array(),OC_Group::usersInGroup($group1)); + $this->assertFalse(OC_Group::inGroup($user1,$group1)); + } + + function testMultiBackend(){ + $backend1=new OC_Group_Dummy(); + $backend2=new OC_Group_Dummy(); + OC_Group::useBackend($backend1); + OC_Group::useBackend($backend2); + + $group1=uniqid(); + $group2=uniqid(); + OC_Group::createGroup($group1); + + //groups should be added to the first registered backend + $this->assertEqual(array($group1),$backend1->getGroups()); + $this->assertEqual(array(),$backend2->getGroups()); + + $this->assertEqual(array($group1),OC_Group::getGroups()); + $this->assertTrue(OC_Group::groupExists($group1)); + $this->assertFalse(OC_Group::groupExists($group2)); + + $backend1->createGroup($group2); + + $this->assertEqual(array($group1,$group2),OC_Group::getGroups()); + $this->assertTrue(OC_Group::groupExists($group1)); + $this->assertTrue(OC_Group::groupExists($group2)); + + $user1=uniqid(); + $user2=uniqid(); + + $this->assertFalse(OC_Group::inGroup($user1,$group1)); + $this->assertFalse(OC_Group::inGroup($user2,$group1)); + + + $this->assertTrue(OC_Group::addToGroup($user1,$group1)); + + $this->assertTrue(OC_Group::inGroup($user1,$group1)); + $this->assertFalse(OC_Group::inGroup($user2,$group1)); + $this->assertFalse($backend2->inGroup($user1,$group1)); + + $this->assertFalse(OC_Group::addToGroup($user1,$group1)); + + $this->assertEqual(array($user1),OC_Group::usersInGroup($group1)); + + $this->assertEqual(array($group1),OC_Group::getUserGroups($user1)); + $this->assertEqual(array(),OC_Group::getUserGroups($user2)); + + OC_Group::deleteGroup($group1); + $this->assertEqual(array(),OC_Group::getUserGroups($user1)); + $this->assertEqual(array(),OC_Group::usersInGroup($group1)); + $this->assertFalse(OC_Group::inGroup($user1,$group1)); + } +} diff --git a/tests/lib/group/backend.php b/tests/lib/group/backend.php new file mode 100644 index 0000000000..9405e90aab --- /dev/null +++ b/tests/lib/group/backend.php @@ -0,0 +1,105 @@ +. +* +*/ + +abstract class Test_Group_Backend extends UnitTestCase { + /** + * @var OC_Group_Backend $backend + */ + protected $backend; + + /** + * get a new unique group name + * test cases can override this in order to clean up created groups + * @return array + */ + public function getGroupName(){ + return uniqid('test_'); + } + + /** + * get a new unique user name + * test cases can override this in order to clean up created user + * @return array + */ + public function getUserName(){ + return uniqid('test_'); + } + + public function testAddRemove(){ + //get the number of groups we start with, in case there are exising groups + $startCount=count($this->backend->getGroups()); + + $name1=$this->getGroupName(); + $name2=$this->getGroupName(); + $this->backend->createGroup($name1); + $count=count($this->backend->getGroups())-$startCount; + $this->assertEqual(1,$count); + $this->assertTrue((array_search($name1,$this->backend->getGroups())!==false)); + $this->assertFalse((array_search($name2,$this->backend->getGroups())!==false)); + $this->backend->createGroup($name2); + $count=count($this->backend->getGroups())-$startCount; + $this->assertEqual(2,$count); + $this->assertTrue((array_search($name1,$this->backend->getGroups())!==false)); + $this->assertTrue((array_search($name2,$this->backend->getGroups())!==false)); + + $this->backend->deleteGroup($name2); + $count=count($this->backend->getGroups())-$startCount; + $this->assertEqual(1,$count); + $this->assertTrue((array_search($name1,$this->backend->getGroups())!==false)); + $this->assertFalse((array_search($name2,$this->backend->getGroups())!==false)); + } + + public function testUser(){ + $group1=$this->getGroupName(); + $group2=$this->getGroupName(); + $this->backend->createGroup($group1); + $this->backend->createGroup($group2); + + $user1=$this->getUserName(); + $user2=$this->getUserName(); + + $this->assertFalse($this->backend->inGroup($user1,$group1)); + $this->assertFalse($this->backend->inGroup($user2,$group1)); + $this->assertFalse($this->backend->inGroup($user1,$group2)); + $this->assertFalse($this->backend->inGroup($user2,$group2)); + + $this->assertTrue($this->backend->addToGroup($user1,$group1)); + + $this->assertTrue($this->backend->inGroup($user1,$group1)); + $this->assertFalse($this->backend->inGroup($user2,$group1)); + $this->assertFalse($this->backend->inGroup($user1,$group2)); + $this->assertFalse($this->backend->inGroup($user2,$group2)); + + $this->assertFalse($this->backend->addToGroup($user1,$group1)); + + $this->assertEqual(array($user1),$this->backend->usersInGroup($group1)); + $this->assertEqual(array(),$this->backend->usersInGroup($group2)); + + $this->assertEqual(array($group1),$this->backend->getUserGroups($user1)); + $this->assertEqual(array(),$this->backend->getUserGroups($user2)); + + $this->backend->deleteGroup($group1); + $this->assertEqual(array(),$this->backend->getUserGroups($user1)); + $this->assertEqual(array(),$this->backend->usersInGroup($group1)); + $this->assertFalse($this->backend->inGroup($user1,$group1)); + } +} diff --git a/tests/lib/group/database.php b/tests/lib/group/database.php new file mode 100644 index 0000000000..fb51bc8d8d --- /dev/null +++ b/tests/lib/group/database.php @@ -0,0 +1,55 @@ +. +* +*/ + +class Test_Group_Database extends Test_Group_Backend { + private $groups=array(); + + /** + * get a new unique group name + * test cases can override this in order to clean up created groups + * @return array + */ + public function getGroupName(){ + $name=uniqid('test_'); + $this->groups[]=$name; + return $name; + } + + /** + * get a new unique user name + * test cases can override this in order to clean up created user + * @return array + */ + public function getUserName(){ + return uniqid('test_'); + } + + public function setUp(){ + $this->backend=new OC_Group_Database(); + } + + public function tearDown(){ + foreach($this->groups as $group){ + $this->backend->deleteGroup($group); + } + } +} diff --git a/tests/lib/group/dummy.php b/tests/lib/group/dummy.php new file mode 100644 index 0000000000..d0b475d3ec --- /dev/null +++ b/tests/lib/group/dummy.php @@ -0,0 +1,27 @@ +. +* +*/ + +class Test_Group_Dummy extends Test_Group_Backend { + public function setUp(){ + $this->backend=new OC_Group_Dummy(); + } +} diff --git a/tests/lib/user/backend.php b/tests/lib/user/backend.php new file mode 100644 index 0000000000..5dab5afb18 --- /dev/null +++ b/tests/lib/user/backend.php @@ -0,0 +1,89 @@ +. +* +*/ + +abstract class Test_User_Backend extends UnitTestCase { + /** + * @var OC_User_Backend $backend + */ + protected $backend; + + /** + * get a new unique user name + * test cases can override this in order to clean up created user + * @return array + */ + public function getUser(){ + return uniqid('test_'); + } + + public function testAddRemove(){ + //get the number of groups we start with, in case there are exising groups + $startCount=count($this->backend->getUsers()); + + $name1=$this->getUser(); + $name2=$this->getUser(); + $this->backend->createUser($name1,''); + $count=count($this->backend->getUsers())-$startCount; + $this->assertEqual(1,$count); + $this->assertTrue((array_search($name1,$this->backend->getUsers())!==false)); + $this->assertFalse((array_search($name2,$this->backend->getUsers())!==false)); + $this->backend->createUser($name2,''); + $count=count($this->backend->getUsers())-$startCount; + $this->assertEqual(2,$count); + $this->assertTrue((array_search($name1,$this->backend->getUsers())!==false)); + $this->assertTrue((array_search($name2,$this->backend->getUsers())!==false)); + + $this->backend->deleteUser($name2); + $count=count($this->backend->getUsers())-$startCount; + $this->assertEqual(1,$count); + $this->assertTrue((array_search($name1,$this->backend->getUsers())!==false)); + $this->assertFalse((array_search($name2,$this->backend->getUsers())!==false)); + } + + public function testLogin(){ + $name1=$this->getUser(); + $name2=$this->getUser(); + + $this->assertFalse($this->backend->userExists($name1)); + $this->assertFalse($this->backend->userExists($name2)); + + $this->backend->createUser($name1,'pass1'); + $this->backend->createUser($name2,'pass2'); + + $this->assertTrue($this->backend->userExists($name1)); + $this->assertTrue($this->backend->userExists($name2)); + + $this->assertTrue($this->backend->checkPassword($name1,'pass1')); + $this->assertTrue($this->backend->checkPassword($name2,'pass2')); + + $this->assertFalse($this->backend->checkPassword($name1,'pass2')); + $this->assertFalse($this->backend->checkPassword($name2,'pass1')); + + $this->assertFalse($this->backend->checkPassword($name1,'dummy')); + $this->assertFalse($this->backend->checkPassword($name2,'foobar')); + + $this->backend->setPassword($name1,'newpass1'); + $this->assertFalse($this->backend->checkPassword($name1,'pass1')); + $this->assertTrue($this->backend->checkPassword($name1,'newpass1')); + $this->assertFalse($this->backend->checkPassword($name2,'newpass1')); + } +} diff --git a/tests/lib/user/database.php b/tests/lib/user/database.php new file mode 100644 index 0000000000..b2fcce93c5 --- /dev/null +++ b/tests/lib/user/database.php @@ -0,0 +1,45 @@ +. +* +*/ + +class Test_User_Database extends Test_User_Backend { + private $user=array(); + /** + * get a new unique user name + * test cases can override this in order to clean up created user + * @return array + */ + public function getUser(){ + $user=uniqid('test_'); + $this->users[]=$user; + return $user; + } + + public function setUp(){ + $this->backend=new OC_User_Dummy(); + } + + public function tearDown(){ + foreach($this->users as $user){ + $this->backend->deleteUser($user); + } + } +} diff --git a/tests/lib/user/dummy.php b/tests/lib/user/dummy.php new file mode 100644 index 0000000000..062f55ba07 --- /dev/null +++ b/tests/lib/user/dummy.php @@ -0,0 +1,27 @@ +. +* +*/ + +class Test_User_Dummy extends Test_User_Backend { + public function setUp(){ + $this->backend=new OC_User_Dummy(); + } +}