Пример #1
0
 public function Analyze()
 {
     $info =& $this->getid3->info;
     if (!Utils::intValueSupported($info['filesize'])) {
         $info['warning'][] = 'Unable to check for ID3v1 because file is larger than ' . round(PHP_INT_MAX / 1073741824) . 'GB';
         return false;
     }
     $this->fseek(-256, SEEK_END);
     $preid3v1 = $this->fread(128);
     $id3v1tag = $this->fread(128);
     if (substr($id3v1tag, 0, 3) == 'TAG') {
         $info['avdataend'] = $info['filesize'] - 128;
         $ParsedID3v1['title'] = $this->cutfield(substr($id3v1tag, 3, 30));
         $ParsedID3v1['artist'] = $this->cutfield(substr($id3v1tag, 33, 30));
         $ParsedID3v1['album'] = $this->cutfield(substr($id3v1tag, 63, 30));
         $ParsedID3v1['year'] = $this->cutfield(substr($id3v1tag, 93, 4));
         $ParsedID3v1['comment'] = substr($id3v1tag, 97, 30);
         // can't remove nulls yet, track detection depends on them
         $ParsedID3v1['genreid'] = ord(substr($id3v1tag, 127, 1));
         // If second-last byte of comment field is null and last byte of comment field is non-null
         // then this is ID3v1.1 and the comment field is 28 bytes long and the 30th byte is the track number
         if ($id3v1tag[125] === "" && $id3v1tag[126] !== "") {
             $ParsedID3v1['track'] = ord(substr($ParsedID3v1['comment'], 29, 1));
             $ParsedID3v1['comment'] = substr($ParsedID3v1['comment'], 0, 28);
         }
         $ParsedID3v1['comment'] = $this->cutfield($ParsedID3v1['comment']);
         $ParsedID3v1['genre'] = $this->LookupGenreName($ParsedID3v1['genreid']);
         if (!empty($ParsedID3v1['genre'])) {
             unset($ParsedID3v1['genreid']);
         }
         if (isset($ParsedID3v1['genre']) && (empty($ParsedID3v1['genre']) || $ParsedID3v1['genre'] == 'Unknown')) {
             unset($ParsedID3v1['genre']);
         }
         foreach ($ParsedID3v1 as $key => $value) {
             $ParsedID3v1['comments'][$key][0] = $value;
         }
         // ID3v1 data is supposed to be padded with NULL characters, but some taggers pad with spaces
         $GoodFormatID3v1tag = $this->GenerateID3v1Tag($ParsedID3v1['title'], $ParsedID3v1['artist'], $ParsedID3v1['album'], $ParsedID3v1['year'], isset($ParsedID3v1['genre']) ? $this->LookupGenreID($ParsedID3v1['genre']) : false, $ParsedID3v1['comment'], !empty($ParsedID3v1['track']) ? $ParsedID3v1['track'] : '');
         $ParsedID3v1['padding_valid'] = true;
         if ($id3v1tag !== $GoodFormatID3v1tag) {
             $ParsedID3v1['padding_valid'] = false;
             $info['warning'][] = 'Some ID3v1 fields do not use NULL characters for padding';
         }
         $ParsedID3v1['tag_offset_end'] = $info['filesize'];
         $ParsedID3v1['tag_offset_start'] = $ParsedID3v1['tag_offset_end'] - 128;
         $info['id3v1'] = $ParsedID3v1;
     }
     if (substr($preid3v1, 0, 3) == 'TAG') {
         // The way iTunes handles tags is, well, brain-damaged.
         // It completely ignores v1 if ID3v2 is present.
         // This goes as far as adding a new v1 tag *even if there already is one*
         // A suspected double-ID3v1 tag has been detected, but it could be that
         // the "TAG" identifier is a legitimate part of an APE or Lyrics3 tag
         if (substr($preid3v1, 96, 8) == 'APETAGEX') {
             // an APE tag footer was found before the last ID3v1, assume false "TAG" synch
         } elseif (substr($preid3v1, 119, 6) == 'LYRICS') {
             // a Lyrics3 tag footer was found before the last ID3v1, assume false "TAG" synch
         } else {
             // APE and Lyrics3 footers not found - assume double ID3v1
             $info['warning'][] = 'Duplicate ID3v1 tag detected - this has been known to happen with iTunes';
             $info['avdataend'] -= 128;
         }
     }
     return true;
 }
Пример #2
0
 public function Analyze()
 {
     $info =& $this->getid3->info;
     $info['fileformat'] = 'zip';
     $info['zip']['encoding'] = 'ISO-8859-1';
     $info['zip']['files'] = array();
     $info['zip']['compressed_size'] = 0;
     $info['zip']['uncompressed_size'] = 0;
     $info['zip']['entries_count'] = 0;
     if (!Utils::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) {
             $this->fseek(-128 * $EOCDsearchCounter, SEEK_END);
             $EOCDsearchData = $this->fread(128) . $EOCDsearchData;
             if (strstr($EOCDsearchData, 'PK' . "")) {
                 $EOCDposition = strpos($EOCDsearchData, 'PK' . "");
                 $this->fseek(-128 * $EOCDsearchCounter + $EOCDposition, SEEK_END);
                 $info['zip']['end_central_directory'] = $this->ZIPparseEndOfCentralDirectory();
                 $this->fseek($info['zip']['end_central_directory']['directory_offset']);
                 $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) { zero-byte files are valid
                     if (!empty($centraldirectoryentry['filename'])) {
                         $info['zip']['files'] = Utils::array_merge_clobber($info['zip']['files'], Utils::CreateDeepArray($centraldirectoryentry['filename'], '/', $centraldirectoryentry['uncompressed_size']));
                     }
                 }
                 if ($info['zip']['entries_count'] == 0) {
                     $info['error'][] = 'No Central Directory entries found (truncated file?)';
                     return false;
                 }
                 if (!empty($info['zip']['end_central_directory']['comment'])) {
                     $info['zip']['comments']['comment'][] = $info['zip']['end_central_directory']['comment'];
                 }
                 if (isset($info['zip']['central_directory'][0]['compression_method'])) {
                     $info['zip']['compression_method'] = $info['zip']['central_directory'][0]['compression_method'];
                 }
                 if (isset($info['zip']['central_directory'][0]['flags']['compression_speed'])) {
                     $info['zip']['compression_speed'] = $info['zip']['central_directory'][0]['flags']['compression_speed'];
                 }
                 if (isset($info['zip']['compression_method']) && $info['zip']['compression_method'] == 'store' && !isset($info['zip']['compression_speed'])) {
                     $info['zip']['compression_speed'] = 'store';
                 }
                 // secondary check - we (should) already have all the info we NEED from the Central Directory above, but scanning each
                 // Local File Header entry will
                 foreach ($info['zip']['central_directory'] as $central_directory_entry) {
                     $this->fseek($central_directory_entry['entry_offset']);
                     if ($fileentry = $this->ZIPparseLocalFileHeader()) {
                         $info['zip']['entries'][] = $fileentry;
                     } else {
                         $info['warning'][] = 'Error parsing Local File Header at offset ' . $central_directory_entry['entry_offset'];
                     }
                 }
                 if (!empty($info['zip']['files']['[Content_Types].xml']) && !empty($info['zip']['files']['_rels']['.rels']) && !empty($info['zip']['files']['docProps']['app.xml']) && !empty($info['zip']['files']['docProps']['core.xml'])) {
                     // http://technet.microsoft.com/en-us/library/cc179224.aspx
                     $info['fileformat'] = 'zip.msoffice';
                     if (!empty($ThisFileInfo['zip']['files']['ppt'])) {
                         $info['mime_type'] = 'application/vnd.openxmlformats-officedocument.presentationml.presentation';
                     } elseif (!empty($ThisFileInfo['zip']['files']['xl'])) {
                         $info['mime_type'] = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
                     } elseif (!empty($ThisFileInfo['zip']['files']['word'])) {
                         $info['mime_type'] = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
                     }
                 }
                 return true;
             }
         }
     }
     if (!$this->getZIPentriesFilepointer()) {
         unset($info['zip']);
         $info['fileformat'] = '';
         $info['error'][] = 'Cannot find End Of Central Directory (truncated file?)';
         return false;
     }
     // central directory couldn't be found and/or parsed
     // scan through actual file data entries, recover as much as possible from probable trucated file
     if ($info['zip']['compressed_size'] > $info['filesize'] - 46 - 22) {
         $info['error'][] = 'Warning: Truncated file! - Total compressed file sizes (' . $info['zip']['compressed_size'] . ' bytes) is greater than filesize minus Central Directory and End Of Central Directory structures (' . ($info['filesize'] - 46 - 22) . ' bytes)';
     }
     $info['error'][] = 'Cannot find End Of Central Directory - returned list of files in [zip][entries] array may not be complete';
     foreach ($info['zip']['entries'] as $key => $valuearray) {
         $info['zip']['files'][$valuearray['filename']] = $valuearray['uncompressed_size'];
     }
     return true;
 }
Пример #3
0
 protected function fseek($bytes, $whence = SEEK_SET)
 {
     if ($this->data_string_flag) {
         switch ($whence) {
             case SEEK_SET:
                 $this->data_string_position = $bytes;
                 break;
             case SEEK_CUR:
                 $this->data_string_position += $bytes;
                 break;
             case SEEK_END:
                 $this->data_string_position = $this->data_string_length + $bytes;
                 break;
         }
         return 0;
     } else {
         $pos = $bytes;
         if ($whence == SEEK_CUR) {
             $pos = $this->ftell() + $bytes;
         } elseif ($whence == SEEK_END) {
             $pos = $this->getid3->info['filesize'] + $bytes;
         }
         if (!Utils::intValueSupported($pos)) {
             throw new Exception('cannot fseek(' . $pos . ') because beyond PHP filesystem limit', 10);
         }
     }
     return fseek($this->getid3->fp, $bytes, $whence);
 }
Пример #4
0
 public function getLyrics3Data($endoffset, $version, $length)
 {
     // http://www.volweb.cz/str/tags.htm
     $info =& $this->getid3->info;
     if (!Utils::intValueSupported($endoffset)) {
         $info['warning'][] = 'Unable to check for Lyrics3 because file is larger than ' . round(PHP_INT_MAX / 1073741824) . 'GB';
         return false;
     }
     $this->fseek($endoffset);
     if ($length <= 0) {
         return false;
     }
     $rawdata = $this->fread($length);
     $ParsedLyrics3['raw']['lyrics3version'] = $version;
     $ParsedLyrics3['raw']['lyrics3tagsize'] = $length;
     $ParsedLyrics3['tag_offset_start'] = $endoffset;
     $ParsedLyrics3['tag_offset_end'] = $endoffset + $length - 1;
     if (substr($rawdata, 0, 11) != 'LYRICSBEGIN') {
         if (strpos($rawdata, 'LYRICSBEGIN') !== false) {
             $info['warning'][] = '"LYRICSBEGIN" expected at ' . $endoffset . ' but actually found at ' . ($endoffset + strpos($rawdata, 'LYRICSBEGIN')) . ' - this is invalid for Lyrics3 v' . $version;
             $info['avdataend'] = $endoffset + strpos($rawdata, 'LYRICSBEGIN');
             $rawdata = substr($rawdata, strpos($rawdata, 'LYRICSBEGIN'));
             $length = strlen($rawdata);
             $ParsedLyrics3['tag_offset_start'] = $info['avdataend'];
             $ParsedLyrics3['raw']['lyrics3tagsize'] = $length;
         } else {
             $info['error'][] = '"LYRICSBEGIN" expected at ' . $endoffset . ' but found "' . substr($rawdata, 0, 11) . '" instead';
             return false;
         }
     }
     switch ($version) {
         case 1:
             if (substr($rawdata, strlen($rawdata) - 9, 9) == 'LYRICSEND') {
                 $ParsedLyrics3['raw']['LYR'] = trim(substr($rawdata, 11, strlen($rawdata) - 11 - 9));
                 $this->Lyrics3LyricsTimestampParse($ParsedLyrics3);
             } else {
                 $info['error'][] = '"LYRICSEND" expected at ' . ($this->ftell() - 11 + $length - 9) . ' but found "' . substr($rawdata, strlen($rawdata) - 9, 9) . '" instead';
                 return false;
             }
             break;
         case 2:
             if (substr($rawdata, strlen($rawdata) - 9, 9) == 'LYRICS200') {
                 $ParsedLyrics3['raw']['unparsed'] = substr($rawdata, 11, strlen($rawdata) - 11 - 9 - 6);
                 // LYRICSBEGIN + LYRICS200 + LSZ
                 $rawdata = $ParsedLyrics3['raw']['unparsed'];
                 while (strlen($rawdata) > 0) {
                     $fieldname = substr($rawdata, 0, 3);
                     $fieldsize = (int) substr($rawdata, 3, 5);
                     $ParsedLyrics3['raw'][$fieldname] = substr($rawdata, 8, $fieldsize);
                     $rawdata = substr($rawdata, 3 + 5 + $fieldsize);
                 }
                 if (isset($ParsedLyrics3['raw']['IND'])) {
                     $i = 0;
                     $flagnames = array('lyrics', 'timestamps', 'inhibitrandom');
                     foreach ($flagnames as $flagname) {
                         if (strlen($ParsedLyrics3['raw']['IND']) > $i++) {
                             $ParsedLyrics3['flags'][$flagname] = $this->IntString2Bool(substr($ParsedLyrics3['raw']['IND'], $i, 1 - 1));
                         }
                     }
                 }
                 $fieldnametranslation = array('ETT' => 'title', 'EAR' => 'artist', 'EAL' => 'album', 'INF' => 'comment', 'AUT' => 'author');
                 foreach ($fieldnametranslation as $key => $value) {
                     if (isset($ParsedLyrics3['raw'][$key])) {
                         $ParsedLyrics3['comments'][$value][] = trim($ParsedLyrics3['raw'][$key]);
                     }
                 }
                 if (isset($ParsedLyrics3['raw']['IMG'])) {
                     $imagestrings = explode("\r\n", $ParsedLyrics3['raw']['IMG']);
                     foreach ($imagestrings as $key => $imagestring) {
                         if (strpos($imagestring, '||') !== false) {
                             $imagearray = explode('||', $imagestring);
                             $ParsedLyrics3['images'][$key]['filename'] = isset($imagearray[0]) ? $imagearray[0] : '';
                             $ParsedLyrics3['images'][$key]['description'] = isset($imagearray[1]) ? $imagearray[1] : '';
                             $ParsedLyrics3['images'][$key]['timestamp'] = $this->Lyrics3Timestamp2Seconds(isset($imagearray[2]) ? $imagearray[2] : '');
                         }
                     }
                 }
                 if (isset($ParsedLyrics3['raw']['LYR'])) {
                     $this->Lyrics3LyricsTimestampParse($ParsedLyrics3);
                 }
             } else {
                 $info['error'][] = '"LYRICS200" expected at ' . ($this->ftell() - 11 + $length - 9) . ' but found "' . substr($rawdata, strlen($rawdata) - 9, 9) . '" instead';
                 return false;
             }
             break;
         default:
             $info['error'][] = 'Cannot process Lyrics3 version ' . $version . ' (only v1 and v2)';
             return false;
             break;
     }
     if (isset($info['id3v1']['tag_offset_start']) && $info['id3v1']['tag_offset_start'] <= $ParsedLyrics3['tag_offset_end']) {
         $info['warning'][] = 'ID3v1 tag information ignored since it appears to be a false synch in Lyrics3 tag data';
         unset($info['id3v1']);
         foreach ($info['warning'] as $key => $value) {
             if ($value == 'Some ID3v1 fields do not use NULL characters for padding') {
                 unset($info['warning'][$key]);
                 sort($info['warning']);
                 break;
             }
         }
     }
     $info['lyrics3'] = $ParsedLyrics3;
     return true;
 }
Пример #5
0
 public function Analyze()
 {
     $info =& $this->getid3->info;
     if (!Utils::intValueSupported($info['filesize'])) {
         $info['warning'][] = 'Unable to check for APEtags because file is larger than ' . round(PHP_INT_MAX / 1073741824) . 'GB';
         return false;
     }
     $id3v1tagsize = 128;
     $apetagheadersize = 32;
     $lyrics3tagsize = 10;
     if ($this->overrideendoffset == 0) {
         $this->fseek(0 - $id3v1tagsize - $apetagheadersize - $lyrics3tagsize, SEEK_END);
         $APEfooterID3v1 = $this->fread($id3v1tagsize + $apetagheadersize + $lyrics3tagsize);
         //if (preg_match('/APETAGEX.{24}TAG.{125}$/i', $APEfooterID3v1)) {
         if (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $id3v1tagsize - $apetagheadersize, 8) == 'APETAGEX') {
             // APE tag found before ID3v1
             $info['ape']['tag_offset_end'] = $info['filesize'] - $id3v1tagsize;
             //} elseif (preg_match('/APETAGEX.{24}$/i', $APEfooterID3v1)) {
         } elseif (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $apetagheadersize, 8) == 'APETAGEX') {
             // APE tag found, no ID3v1
             $info['ape']['tag_offset_end'] = $info['filesize'];
         }
     } else {
         $this->fseek($this->overrideendoffset - $apetagheadersize);
         if ($this->fread(8) == 'APETAGEX') {
             $info['ape']['tag_offset_end'] = $this->overrideendoffset;
         }
     }
     if (!isset($info['ape']['tag_offset_end'])) {
         // APE tag not found
         unset($info['ape']);
         return false;
     }
     // shortcut
     $thisfile_ape =& $info['ape'];
     $this->fseek($thisfile_ape['tag_offset_end'] - $apetagheadersize);
     $APEfooterData = $this->fread(32);
     if (!($thisfile_ape['footer'] = $this->parseAPEheaderFooter($APEfooterData))) {
         $info['error'][] = 'Error parsing APE footer at offset ' . $thisfile_ape['tag_offset_end'];
         return false;
     }
     if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
         $this->fseek($thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'] - $apetagheadersize);
         $thisfile_ape['tag_offset_start'] = $this->ftell();
         $APEtagData = $this->fread($thisfile_ape['footer']['raw']['tagsize'] + $apetagheadersize);
     } else {
         $thisfile_ape['tag_offset_start'] = $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'];
         $this->fseek($thisfile_ape['tag_offset_start']);
         $APEtagData = $this->fread($thisfile_ape['footer']['raw']['tagsize']);
     }
     $info['avdataend'] = $thisfile_ape['tag_offset_start'];
     if (isset($info['id3v1']['tag_offset_start']) && $info['id3v1']['tag_offset_start'] < $thisfile_ape['tag_offset_end']) {
         $info['warning'][] = 'ID3v1 tag information ignored since it appears to be a false synch in APEtag data';
         unset($info['id3v1']);
         foreach ($info['warning'] as $key => $value) {
             if ($value == 'Some ID3v1 fields do not use NULL characters for padding') {
                 unset($info['warning'][$key]);
                 sort($info['warning']);
                 break;
             }
         }
     }
     $offset = 0;
     if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
         if ($thisfile_ape['header'] = $this->parseAPEheaderFooter(substr($APEtagData, 0, $apetagheadersize))) {
             $offset += $apetagheadersize;
         } else {
             $info['error'][] = 'Error parsing APE header at offset ' . $thisfile_ape['tag_offset_start'];
             return false;
         }
     }
     // shortcut
     $info['replay_gain'] = array();
     $thisfile_replaygain =& $info['replay_gain'];
     for ($i = 0; $i < $thisfile_ape['footer']['raw']['tag_items']; $i++) {
         $value_size = Utils::LittleEndian2Int(substr($APEtagData, $offset, 4));
         $offset += 4;
         $item_flags = Utils::LittleEndian2Int(substr($APEtagData, $offset, 4));
         $offset += 4;
         if (strstr(substr($APEtagData, $offset), "") === false) {
             $info['error'][] = 'Cannot find null-byte (0x00) seperator between ItemKey #' . $i . ' and value. ItemKey starts ' . $offset . ' bytes into the APE tag, at file offset ' . ($thisfile_ape['tag_offset_start'] + $offset);
             return false;
         }
         $ItemKeyLength = strpos($APEtagData, "", $offset) - $offset;
         $item_key = strtolower(substr($APEtagData, $offset, $ItemKeyLength));
         // shortcut
         $thisfile_ape['items'][$item_key] = array();
         $thisfile_ape_items_current =& $thisfile_ape['items'][$item_key];
         $thisfile_ape_items_current['offset'] = $thisfile_ape['tag_offset_start'] + $offset;
         $offset += $ItemKeyLength + 1;
         // skip 0x00 terminator
         $thisfile_ape_items_current['data'] = substr($APEtagData, $offset, $value_size);
         $offset += $value_size;
         $thisfile_ape_items_current['flags'] = $this->parseAPEtagFlags($item_flags);
         switch ($thisfile_ape_items_current['flags']['item_contents_raw']) {
             case 0:
                 // UTF-8
             // UTF-8
             case 2:
                 // Locator (URL, filename, etc), UTF-8 encoded
                 $thisfile_ape_items_current['data'] = explode("", $thisfile_ape_items_current['data']);
                 break;
             case 1:
                 // binary data
             // binary data
             default:
                 break;
         }
         switch (strtolower($item_key)) {
             // http://wiki.hydrogenaud.io/index.php?title=ReplayGain#MP3Gain
             case 'replaygain_track_gain':
                 if (preg_match('#^[\\-\\+][0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) {
                     $thisfile_replaygain['track']['adjustment'] = (double) str_replace(',', '.', $thisfile_ape_items_current['data'][0]);
                     // float casting will see "0,95" as zero!
                     $thisfile_replaygain['track']['originator'] = 'unspecified';
                 } else {
                     $info['warning'][] = 'MP3gainTrackGain value in APEtag appears invalid: "' . $thisfile_ape_items_current['data'][0] . '"';
                 }
                 break;
             case 'replaygain_track_peak':
                 if (preg_match('#^[0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) {
                     $thisfile_replaygain['track']['peak'] = (double) str_replace(',', '.', $thisfile_ape_items_current['data'][0]);
                     // float casting will see "0,95" as zero!
                     $thisfile_replaygain['track']['originator'] = 'unspecified';
                     if ($thisfile_replaygain['track']['peak'] <= 0) {
                         $info['warning'][] = 'ReplayGain Track peak from APEtag appears invalid: ' . $thisfile_replaygain['track']['peak'] . ' (original value = "' . $thisfile_ape_items_current['data'][0] . '")';
                     }
                 } else {
                     $info['warning'][] = 'MP3gainTrackPeak value in APEtag appears invalid: "' . $thisfile_ape_items_current['data'][0] . '"';
                 }
                 break;
             case 'replaygain_album_gain':
                 if (preg_match('#^[\\-\\+][0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) {
                     $thisfile_replaygain['album']['adjustment'] = (double) str_replace(',', '.', $thisfile_ape_items_current['data'][0]);
                     // float casting will see "0,95" as zero!
                     $thisfile_replaygain['album']['originator'] = 'unspecified';
                 } else {
                     $info['warning'][] = 'MP3gainAlbumGain value in APEtag appears invalid: "' . $thisfile_ape_items_current['data'][0] . '"';
                 }
                 break;
             case 'replaygain_album_peak':
                 if (preg_match('#^[0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) {
                     $thisfile_replaygain['album']['peak'] = (double) str_replace(',', '.', $thisfile_ape_items_current['data'][0]);
                     // float casting will see "0,95" as zero!
                     $thisfile_replaygain['album']['originator'] = 'unspecified';
                     if ($thisfile_replaygain['album']['peak'] <= 0) {
                         $info['warning'][] = 'ReplayGain Album peak from APEtag appears invalid: ' . $thisfile_replaygain['album']['peak'] . ' (original value = "' . $thisfile_ape_items_current['data'][0] . '")';
                     }
                 } else {
                     $info['warning'][] = 'MP3gainAlbumPeak value in APEtag appears invalid: "' . $thisfile_ape_items_current['data'][0] . '"';
                 }
                 break;
             case 'mp3gain_undo':
                 if (preg_match('#^[\\-\\+][0-9]{3},[\\-\\+][0-9]{3},[NW]$#', $thisfile_ape_items_current['data'][0])) {
                     list($mp3gain_undo_left, $mp3gain_undo_right, $mp3gain_undo_wrap) = explode(',', $thisfile_ape_items_current['data'][0]);
                     $thisfile_replaygain['mp3gain']['undo_left'] = intval($mp3gain_undo_left);
                     $thisfile_replaygain['mp3gain']['undo_right'] = intval($mp3gain_undo_right);
                     $thisfile_replaygain['mp3gain']['undo_wrap'] = $mp3gain_undo_wrap == 'Y' ? true : false;
                 } else {
                     $info['warning'][] = 'MP3gainUndo value in APEtag appears invalid: "' . $thisfile_ape_items_current['data'][0] . '"';
                 }
                 break;
             case 'mp3gain_minmax':
                 if (preg_match('#^[0-9]{3},[0-9]{3}$#', $thisfile_ape_items_current['data'][0])) {
                     list($mp3gain_globalgain_min, $mp3gain_globalgain_max) = explode(',', $thisfile_ape_items_current['data'][0]);
                     $thisfile_replaygain['mp3gain']['globalgain_track_min'] = intval($mp3gain_globalgain_min);
                     $thisfile_replaygain['mp3gain']['globalgain_track_max'] = intval($mp3gain_globalgain_max);
                 } else {
                     $info['warning'][] = 'MP3gainMinMax value in APEtag appears invalid: "' . $thisfile_ape_items_current['data'][0] . '"';
                 }
                 break;
             case 'mp3gain_album_minmax':
                 if (preg_match('#^[0-9]{3},[0-9]{3}$#', $thisfile_ape_items_current['data'][0])) {
                     list($mp3gain_globalgain_album_min, $mp3gain_globalgain_album_max) = explode(',', $thisfile_ape_items_current['data'][0]);
                     $thisfile_replaygain['mp3gain']['globalgain_album_min'] = intval($mp3gain_globalgain_album_min);
                     $thisfile_replaygain['mp3gain']['globalgain_album_max'] = intval($mp3gain_globalgain_album_max);
                 } else {
                     $info['warning'][] = 'MP3gainAlbumMinMax value in APEtag appears invalid: "' . $thisfile_ape_items_current['data'][0] . '"';
                 }
                 break;
             case 'tracknumber':
                 if (is_array($thisfile_ape_items_current['data'])) {
                     foreach ($thisfile_ape_items_current['data'] as $comment) {
                         $thisfile_ape['comments']['track'][] = $comment;
                     }
                 }
                 break;
             case 'cover art (artist)':
             case 'cover art (back)':
             case 'cover art (band logo)':
             case 'cover art (band)':
             case 'cover art (colored fish)':
             case 'cover art (composer)':
             case 'cover art (conductor)':
             case 'cover art (front)':
             case 'cover art (icon)':
             case 'cover art (illustration)':
             case 'cover art (lead)':
             case 'cover art (leaflet)':
             case 'cover art (lyricist)':
             case 'cover art (media)':
             case 'cover art (movie scene)':
             case 'cover art (other icon)':
             case 'cover art (other)':
             case 'cover art (performance)':
             case 'cover art (publisher logo)':
             case 'cover art (recording)':
             case 'cover art (studio)':
                 // list of possible cover arts from http://taglib-sharp.sourcearchive.com/documentation/2.0.3.0-2/Ape_2Tag_8cs-source.html
                 if (is_array($thisfile_ape_items_current['data'])) {
                     $info['warning'][] = 'APEtag "' . $item_key . '" should be flagged as Binary data, but was incorrectly flagged as UTF-8';
                     $thisfile_ape_items_current['data'] = implode("", $thisfile_ape_items_current['data']);
                 }
                 list($thisfile_ape_items_current['filename'], $thisfile_ape_items_current['data']) = explode("", $thisfile_ape_items_current['data'], 2);
                 $thisfile_ape_items_current['data_offset'] = $thisfile_ape_items_current['offset'] + strlen($thisfile_ape_items_current['filename'] . "");
                 $thisfile_ape_items_current['data_length'] = strlen($thisfile_ape_items_current['data']);
                 do {
                     $thisfile_ape_items_current['image_mime'] = '';
                     $imageinfo = array();
                     $imagechunkcheck = Utils::GetDataImageSize($thisfile_ape_items_current['data'], $imageinfo);
                     if ($imagechunkcheck === false || !isset($imagechunkcheck[2])) {
                         $info['warning'][] = 'APEtag "' . $item_key . '" contains invalid image data';
                         break;
                     }
                     $thisfile_ape_items_current['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]);
                     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();
                         }
                         $comments_picture_data = array();
                         foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) {
                             if (isset($thisfile_ape_items_current[$picture_key])) {
                                 $comments_picture_data[$picture_key] = $thisfile_ape_items_current[$picture_key];
                             }
                         }
                         $info['ape']['comments']['picture'][] = $comments_picture_data;
                         unset($comments_picture_data);
                     }
                 } while (false);
                 break;
             default:
                 if (is_array($thisfile_ape_items_current['data'])) {
                     foreach ($thisfile_ape_items_current['data'] as $comment) {
                         $thisfile_ape['comments'][strtolower($item_key)][] = $comment;
                     }
                 }
                 break;
         }
     }
     if (empty($thisfile_replaygain)) {
         unset($info['replay_gain']);
     }
     return true;
 }
Пример #6
0
 public function getAACADTSheaderFilepointer($MaxFramesToScan = 1000000, $ReturnExtendedInfo = false)
 {
     $info =& $this->getid3->info;
     // based loosely on code from AACfile by Jurgen Faul  <jfaulØgmx.de>
     // http://jfaul.de/atl  or  http://j-faul.virtualave.net/atl/atl.html
     // http://faac.sourceforge.net/wiki/index.php?page=ADTS // dead link
     // http://wiki.multimedia.cx/index.php?title=ADTS
     // * ADTS Fixed Header: these don't change from frame to frame
     // syncword                                       12    always: '111111111111'
     // ID                                              1    0: MPEG-4, 1: MPEG-2
     // MPEG layer                                      2    If you send AAC in MPEG-TS, set to 0
     // protection_absent                               1    0: CRC present; 1: no CRC
     // profile                                         2    0: AAC Main; 1: AAC LC (Low Complexity); 2: AAC SSR (Scalable Sample Rate); 3: AAC LTP (Long Term Prediction)
     // sampling_frequency_index                        4    15 not allowed
     // private_bit                                     1    usually 0
     // channel_configuration                           3
     // original/copy                                   1    0: original; 1: copy
     // home                                            1    usually 0
     // emphasis                                        2    only if ID == 0 (ie MPEG-4)  // not present in some documentation?
     // * ADTS Variable Header: these can change from frame to frame
     // copyright_identification_bit                    1
     // copyright_identification_start                  1
     // aac_frame_length                               13    length of the frame including header (in bytes)
     // adts_buffer_fullness                           11    0x7FF indicates VBR
     // no_raw_data_blocks_in_frame                     2
     // * ADTS Error check
     // crc_check                                      16    only if protection_absent == 0
     $byteoffset = $info['avdataoffset'];
     $framenumber = 0;
     // Init bit pattern array
     static $decbin = array();
     // Populate $bindec
     for ($i = 0; $i < 256; $i++) {
         $decbin[chr($i)] = str_pad(decbin($i), 8, '0', STR_PAD_LEFT);
     }
     // used to calculate bitrate below
     $BitrateCache = array();
     while (true) {
         // breaks out when end-of-file encountered, or invalid data found,
         // or MaxFramesToScan frames have been scanned
         if (!Utils::intValueSupported($byteoffset)) {
             $info['warning'][] = 'Unable to parse AAC file beyond ' . $this->ftell() . ' (PHP does not support file operations beyond ' . round(PHP_INT_MAX / 1073741824) . 'GB)';
             return false;
         }
         $this->fseek($byteoffset);
         // First get substring
         $substring = $this->fread(9);
         // header is 7 bytes (or 9 if CRC is present)
         $substringlength = strlen($substring);
         if ($substringlength != 9) {
             $info['error'][] = 'Failed to read 7 bytes at offset ' . ($this->ftell() - $substringlength) . ' (only read ' . $substringlength . ' bytes)';
             return false;
         }
         // this would be easier with 64-bit math, but split it up to allow for 32-bit:
         $header1 = Utils::BigEndian2Int(substr($substring, 0, 2));
         $header2 = Utils::BigEndian2Int(substr($substring, 2, 4));
         $header3 = Utils::BigEndian2Int(substr($substring, 6, 1));
         $info['aac']['header']['raw']['syncword'] = ($header1 & 0xfff0) >> 4;
         if ($info['aac']['header']['raw']['syncword'] != 0xfff) {
             $info['error'][] = 'Synch pattern (0x0FFF) not found at offset ' . ($this->ftell() - $substringlength) . ' (found 0x0' . strtoupper(dechex($info['aac']['header']['raw']['syncword'])) . ' instead)';
             //if ($info['fileformat'] == 'aac') {
             //	return true;
             //}
             unset($info['aac']);
             return false;
         }
         // Gather info for first frame only - this takes time to do 1000 times!
         if ($framenumber == 0) {
             $info['aac']['header_type'] = 'ADTS';
             $info['fileformat'] = 'aac';
             $info['audio']['dataformat'] = 'aac';
             $info['aac']['header']['raw']['mpeg_version'] = ($header1 & 0x8) >> 3;
             $info['aac']['header']['raw']['mpeg_layer'] = ($header1 & 0x6) >> 1;
             $info['aac']['header']['raw']['protection_absent'] = ($header1 & 0x1) >> 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 & 0x2000000) >> 25;
             $info['aac']['header']['raw']['channels_code'] = ($header2 & 0x1c00000) >> 22;
             $info['aac']['header']['raw']['original'] = ($header2 & 0x200000) >> 21;
             $info['aac']['header']['raw']['home'] = ($header2 & 0x100000) >> 20;
             $info['aac']['header']['raw']['copyright_stream'] = ($header2 & 0x80000) >> 19;
             $info['aac']['header']['raw']['copyright_start'] = ($header2 & 0x40000) >> 18;
             $info['aac']['header']['raw']['frame_length'] = ($header2 & 0x3ffe0) >> 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 & 0x3ffe0) >> 5;
         if (!isset($BitrateCache[$FrameLength])) {
             $BitrateCache[$FrameLength] = $info['aac']['header']['sample_frequency'] / 1024 * $FrameLength * 8;
         }
         Utils::safe_inc($info['aac']['bitrate_distribution'][$BitrateCache[$FrameLength]], 1);
         $info['aac'][$framenumber]['aac_frame_length'] = $FrameLength;
         $info['aac'][$framenumber]['adts_buffer_fullness'] = ($header2 & 0x1f) << 6 & ($header3 & 0xfc) >> 2;
         if ($info['aac'][$framenumber]['adts_buffer_fullness'] == 0x7ff) {
             $info['audio']['bitrate_mode'] = 'vbr';
         } else {
             $info['audio']['bitrate_mode'] = 'cbr';
         }
         $info['aac'][$framenumber]['num_raw_data_blocks'] = ($header3 & 0x3) >> 0;
         if ($info['aac']['header']['crc_present']) {
             //$info['aac'][$framenumber]['crc'] = Utils::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;
         	Utils::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.
 }
Пример #7
0
 public function Analyze()
 {
     $info =& $this->getid3->info;
     // shortcut
     $info['bonk'] = array();
     $thisfile_bonk =& $info['bonk'];
     $thisfile_bonk['dataoffset'] = $info['avdataoffset'];
     $thisfile_bonk['dataend'] = $info['avdataend'];
     if (!Utils::intValueSupported($thisfile_bonk['dataend'])) {
         $info['warning'][] = 'Unable to parse BONK file from end (v0.6+ preferred method) because PHP filesystem functions only support up to ' . round(PHP_INT_MAX / 1073741824) . 'GB';
     } else {
         // scan-from-end method, for v0.6 and higher
         $this->fseek($thisfile_bonk['dataend'] - 8);
         $PossibleBonkTag = $this->fread(8);
         while ($this->BonkIsValidTagName(substr($PossibleBonkTag, 4, 4), true)) {
             $BonkTagSize = Utils::LittleEndian2Int(substr($PossibleBonkTag, 0, 4));
             $this->fseek(0 - $BonkTagSize, SEEK_CUR);
             $BonkTagOffset = $this->ftell();
             $TagHeaderTest = $this->fread(5);
             if ($TagHeaderTest[0] != "" || substr($PossibleBonkTag, 4, 4) != strtolower(substr($PossibleBonkTag, 4, 4))) {
                 $info['error'][] = 'Expecting "' . Utils::PrintHexBytes("" . strtoupper(substr($PossibleBonkTag, 4, 4))) . '" at offset ' . $BonkTagOffset . ', found "' . Utils::PrintHexBytes($TagHeaderTest) . '"';
                 return false;
             }
             $BonkTagName = substr($TagHeaderTest, 1, 4);
             $thisfile_bonk[$BonkTagName]['size'] = $BonkTagSize;
             $thisfile_bonk[$BonkTagName]['offset'] = $BonkTagOffset;
             $this->HandleBonkTags($BonkTagName);
             $NextTagEndOffset = $BonkTagOffset - 8;
             if ($NextTagEndOffset < $thisfile_bonk['dataoffset']) {
                 if (empty($info['audio']['encoder'])) {
                     $info['audio']['encoder'] = 'Extended BONK v0.9+';
                 }
                 return true;
             }
             $this->fseek($NextTagEndOffset);
             $PossibleBonkTag = $this->fread(8);
         }
     }
     // seek-from-beginning method for v0.4 and v0.5
     if (empty($thisfile_bonk['BONK'])) {
         $this->fseek($thisfile_bonk['dataoffset']);
         do {
             $TagHeaderTest = $this->fread(5);
             switch ($TagHeaderTest) {
                 case "" . 'BONK':
                     if (empty($info['audio']['encoder'])) {
                         $info['audio']['encoder'] = 'BONK v0.4';
                     }
                     break;
                 case "" . 'INFO':
                     $info['audio']['encoder'] = 'Extended BONK v0.5';
                     break;
                 default:
                     break 2;
             }
             $BonkTagName = substr($TagHeaderTest, 1, 4);
             $thisfile_bonk[$BonkTagName]['size'] = $thisfile_bonk['dataend'] - $thisfile_bonk['dataoffset'];
             $thisfile_bonk[$BonkTagName]['offset'] = $thisfile_bonk['dataoffset'];
             $this->HandleBonkTags($BonkTagName);
         } while (true);
     }
     // parse META block for v0.6 - v0.8
     if (empty($thisfile_bonk['INFO']) && isset($thisfile_bonk['META']['tags']['info'])) {
         $this->fseek($thisfile_bonk['META']['tags']['info']);
         $TagHeaderTest = $this->fread(5);
         if ($TagHeaderTest == "" . 'INFO') {
             $info['audio']['encoder'] = 'Extended BONK v0.6 - v0.8';
             $BonkTagName = substr($TagHeaderTest, 1, 4);
             $thisfile_bonk[$BonkTagName]['size'] = $thisfile_bonk['dataend'] - $thisfile_bonk['dataoffset'];
             $thisfile_bonk[$BonkTagName]['offset'] = $thisfile_bonk['dataoffset'];
             $this->HandleBonkTags($BonkTagName);
         }
     }
     if (empty($info['audio']['encoder'])) {
         $info['audio']['encoder'] = 'Extended BONK v0.9+';
     }
     if (empty($thisfile_bonk['BONK'])) {
         unset($info['bonk']);
     }
     return true;
 }
Пример #8
0
 public function RemoveID3v1()
 {
     // File MUST be writeable - CHMOD(646) at least
     if (!empty($this->filename) && is_readable($this->filename) && is_writable($this->filename) && is_file($this->filename)) {
         $this->setRealFileSize();
         if ($this->filesize <= 0 || !Utils::intValueSupported($this->filesize)) {
             $this->errors[] = 'Unable to RemoveID3v1(' . $this->filename . ') because filesize (' . $this->filesize . ') is larger than ' . round(PHP_INT_MAX / 1073741824) . 'GB';
             return false;
         }
         if ($fp_source = fopen($this->filename, 'r+b')) {
             fseek($fp_source, -128, SEEK_END);
             if (fread($fp_source, 3) == 'TAG') {
                 ftruncate($fp_source, $this->filesize - 128);
             } else {
                 // no ID3v1 tag to begin with - do nothing
             }
             fclose($fp_source);
             return true;
         } else {
             $this->errors[] = 'Could not fopen(' . $this->filename . ', "r+b")';
         }
     } else {
         $this->errors[] = $this->filename . ' is not writeable';
     }
     return false;
 }