home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress2 / wp-includes / ID3 / module.tag.id3v1.php < prev    next >
Encoding:
PHP Script  |  2017-07-31  |  12.4 KB  |  382 lines

  1. <?php
  2. /////////////////////////////////////////////////////////////////
  3. /// getID3() by James Heinrich <info@getid3.org>               //
  4. //  available at http://getid3.sourceforge.net                 //
  5. //            or http://www.getid3.org                         //
  6. //          also https://github.com/JamesHeinrich/getID3       //
  7. /////////////////////////////////////////////////////////////////
  8. // See readme.txt for more details                             //
  9. /////////////////////////////////////////////////////////////////
  10. //                                                             //
  11. // module.tag.id3v1.php                                        //
  12. // module for analyzing ID3v1 tags                             //
  13. // dependencies: NONE                                          //
  14. //                                                            ///
  15. /////////////////////////////////////////////////////////////////
  16.  
  17.  
  18. class getid3_id3v1 extends getid3_handler
  19. {
  20.  
  21.     public function Analyze() {
  22.         $info = &$this->getid3->info;
  23.  
  24.         if (!getid3_lib::intValueSupported($info['filesize'])) {
  25.             $this->warning('Unable to check for ID3v1 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB');
  26.             return false;
  27.         }
  28.  
  29.         $this->fseek(-256, SEEK_END);
  30.         $preid3v1 = $this->fread(128);
  31.         $id3v1tag = $this->fread(128);
  32.  
  33.         if (substr($id3v1tag, 0, 3) == 'TAG') {
  34.  
  35.             $info['avdataend'] = $info['filesize'] - 128;
  36.  
  37.             $ParsedID3v1['title']   = $this->cutfield(substr($id3v1tag,   3, 30));
  38.             $ParsedID3v1['artist']  = $this->cutfield(substr($id3v1tag,  33, 30));
  39.             $ParsedID3v1['album']   = $this->cutfield(substr($id3v1tag,  63, 30));
  40.             $ParsedID3v1['year']    = $this->cutfield(substr($id3v1tag,  93,  4));
  41.             $ParsedID3v1['comment'] =                 substr($id3v1tag,  97, 30);  // can't remove nulls yet, track detection depends on them
  42.             $ParsedID3v1['genreid'] =             ord(substr($id3v1tag, 127,  1));
  43.  
  44.             // If second-last byte of comment field is null and last byte of comment field is non-null
  45.             // then this is ID3v1.1 and the comment field is 28 bytes long and the 30th byte is the track number
  46.             if (($id3v1tag{125} === "\x00") && ($id3v1tag{126} !== "\x00")) {
  47.                 $ParsedID3v1['track']   = ord(substr($ParsedID3v1['comment'], 29,  1));
  48.                 $ParsedID3v1['comment'] =     substr($ParsedID3v1['comment'],  0, 28);
  49.             }
  50.             $ParsedID3v1['comment'] = $this->cutfield($ParsedID3v1['comment']);
  51.  
  52.             $ParsedID3v1['genre'] = $this->LookupGenreName($ParsedID3v1['genreid']);
  53.             if (!empty($ParsedID3v1['genre'])) {
  54.                 unset($ParsedID3v1['genreid']);
  55.             }
  56.             if (isset($ParsedID3v1['genre']) && (empty($ParsedID3v1['genre']) || ($ParsedID3v1['genre'] == 'Unknown'))) {
  57.                 unset($ParsedID3v1['genre']);
  58.             }
  59.  
  60.             foreach ($ParsedID3v1 as $key => $value) {
  61.                 $ParsedID3v1['comments'][$key][0] = $value;
  62.             }
  63.             // ID3v1 encoding detection hack START
  64.             // ID3v1 is defined as always using ISO-8859-1 encoding, but it is not uncommon to find files tagged with ID3v1 using Windows-1251 or other character sets
  65.             // Since ID3v1 has no concept of character sets there is no certain way to know we have the correct non-ISO-8859-1 character set, but we can guess
  66.             $ID3v1encoding = 'ISO-8859-1';
  67.             foreach ($ParsedID3v1['comments'] as $tag_key => $valuearray) {
  68.                 foreach ($valuearray as $key => $value) {
  69.                     if (preg_match('#^[\\x00-\\x40\\xA8\\B8\\x80-\\xFF]+$#', $value)) {
  70.                         foreach (array('Windows-1251', 'KOI8-R') as $id3v1_bad_encoding) {
  71.                             if (function_exists('mb_convert_encoding') && @mb_convert_encoding($value, $id3v1_bad_encoding, $id3v1_bad_encoding) === $value) {
  72.                                 $ID3v1encoding = $id3v1_bad_encoding;
  73.                                 break 3;
  74.                             } elseif (function_exists('iconv') && @iconv($id3v1_bad_encoding, $id3v1_bad_encoding, $value) === $value) {
  75.                                 $ID3v1encoding = $id3v1_bad_encoding;
  76.                                 break 3;
  77.                             }
  78.                         }
  79.                     }
  80.                 }
  81.             }
  82.             // ID3v1 encoding detection hack END
  83.  
  84.             // ID3v1 data is supposed to be padded with NULL characters, but some taggers pad with spaces
  85.             $GoodFormatID3v1tag = $this->GenerateID3v1Tag(
  86.                                             $ParsedID3v1['title'],
  87.                                             $ParsedID3v1['artist'],
  88.                                             $ParsedID3v1['album'],
  89.                                             $ParsedID3v1['year'],
  90.                                             (isset($ParsedID3v1['genre']) ? $this->LookupGenreID($ParsedID3v1['genre']) : false),
  91.                                             $ParsedID3v1['comment'],
  92.                                             (!empty($ParsedID3v1['track']) ? $ParsedID3v1['track'] : ''));
  93.             $ParsedID3v1['padding_valid'] = true;
  94.             if ($id3v1tag !== $GoodFormatID3v1tag) {
  95.                 $ParsedID3v1['padding_valid'] = false;
  96.                 $this->warning('Some ID3v1 fields do not use NULL characters for padding');
  97.             }
  98.  
  99.             $ParsedID3v1['tag_offset_end']   = $info['filesize'];
  100.             $ParsedID3v1['tag_offset_start'] = $ParsedID3v1['tag_offset_end'] - 128;
  101.  
  102.             $info['id3v1'] = $ParsedID3v1;
  103.             $info['id3v1']['encoding'] = $ID3v1encoding;
  104.         }
  105.  
  106.         if (substr($preid3v1, 0, 3) == 'TAG') {
  107.             // The way iTunes handles tags is, well, brain-damaged.
  108.             // It completely ignores v1 if ID3v2 is present.
  109.             // This goes as far as adding a new v1 tag *even if there already is one*
  110.  
  111.             // A suspected double-ID3v1 tag has been detected, but it could be that
  112.             // the "TAG" identifier is a legitimate part of an APE or Lyrics3 tag
  113.             if (substr($preid3v1, 96, 8) == 'APETAGEX') {
  114.                 // an APE tag footer was found before the last ID3v1, assume false "TAG" synch
  115.             } elseif (substr($preid3v1, 119, 6) == 'LYRICS') {
  116.                 // a Lyrics3 tag footer was found before the last ID3v1, assume false "TAG" synch
  117.             } else {
  118.                 // APE and Lyrics3 footers not found - assume double ID3v1
  119.                 $this->warning('Duplicate ID3v1 tag detected - this has been known to happen with iTunes');
  120.                 $info['avdataend'] -= 128;
  121.             }
  122.         }
  123.  
  124.         return true;
  125.     }
  126.  
  127.     public static function cutfield($str) {
  128.         return trim(substr($str, 0, strcspn($str, "\x00")));
  129.     }
  130.  
  131.     public static function ArrayOfGenres($allowSCMPXextended=false) {
  132.         static $GenreLookup = array(
  133.             0    => 'Blues',
  134.             1    => 'Classic Rock',
  135.             2    => 'Country',
  136.             3    => 'Dance',
  137.             4    => 'Disco',
  138.             5    => 'Funk',
  139.             6    => 'Grunge',
  140.             7    => 'Hip-Hop',
  141.             8    => 'Jazz',
  142.             9    => 'Metal',
  143.             10   => 'New Age',
  144.             11   => 'Oldies',
  145.             12   => 'Other',
  146.             13   => 'Pop',
  147.             14   => 'R&B',
  148.             15   => 'Rap',
  149.             16   => 'Reggae',
  150.             17   => 'Rock',
  151.             18   => 'Techno',
  152.             19   => 'Industrial',
  153.             20   => 'Alternative',
  154.             21   => 'Ska',
  155.             22   => 'Death Metal',
  156.             23   => 'Pranks',
  157.             24   => 'Soundtrack',
  158.             25   => 'Euro-Techno',
  159.             26   => 'Ambient',
  160.             27   => 'Trip-Hop',
  161.             28   => 'Vocal',
  162.             29   => 'Jazz+Funk',
  163.             30   => 'Fusion',
  164.             31   => 'Trance',
  165.             32   => 'Classical',
  166.             33   => 'Instrumental',
  167.             34   => 'Acid',
  168.             35   => 'House',
  169.             36   => 'Game',
  170.             37   => 'Sound Clip',
  171.             38   => 'Gospel',
  172.             39   => 'Noise',
  173.             40   => 'Alt. Rock',
  174.             41   => 'Bass',
  175.             42   => 'Soul',
  176.             43   => 'Punk',
  177.             44   => 'Space',
  178.             45   => 'Meditative',
  179.             46   => 'Instrumental Pop',
  180.             47   => 'Instrumental Rock',
  181.             48   => 'Ethnic',
  182.             49   => 'Gothic',
  183.             50   => 'Darkwave',
  184.             51   => 'Techno-Industrial',
  185.             52   => 'Electronic',
  186.             53   => 'Pop-Folk',
  187.             54   => 'Eurodance',
  188.             55   => 'Dream',
  189.             56   => 'Southern Rock',
  190.             57   => 'Comedy',
  191.             58   => 'Cult',
  192.             59   => 'Gangsta Rap',
  193.             60   => 'Top 40',
  194.             61   => 'Christian Rap',
  195.             62   => 'Pop/Funk',
  196.             63   => 'Jungle',
  197.             64   => 'Native American',
  198.             65   => 'Cabaret',
  199.             66   => 'New Wave',
  200.             67   => 'Psychedelic',
  201.             68   => 'Rave',
  202.             69   => 'Showtunes',
  203.             70   => 'Trailer',
  204.             71   => 'Lo-Fi',
  205.             72   => 'Tribal',
  206.             73   => 'Acid Punk',
  207.             74   => 'Acid Jazz',
  208.             75   => 'Polka',
  209.             76   => 'Retro',
  210.             77   => 'Musical',
  211.             78   => 'Rock & Roll',
  212.             79   => 'Hard Rock',
  213.             80   => 'Folk',
  214.             81   => 'Folk/Rock',
  215.             82   => 'National Folk',
  216.             83   => 'Swing',
  217.             84   => 'Fast-Fusion',
  218.             85   => 'Bebob',
  219.             86   => 'Latin',
  220.             87   => 'Revival',
  221.             88   => 'Celtic',
  222.             89   => 'Bluegrass',
  223.             90   => 'Avantgarde',
  224.             91   => 'Gothic Rock',
  225.             92   => 'Progressive Rock',
  226.             93   => 'Psychedelic Rock',
  227.             94   => 'Symphonic Rock',
  228.             95   => 'Slow Rock',
  229.             96   => 'Big Band',
  230.             97   => 'Chorus',
  231.             98   => 'Easy Listening',
  232.             99   => 'Acoustic',
  233.             100  => 'Humour',
  234.             101  => 'Speech',
  235.             102  => 'Chanson',
  236.             103  => 'Opera',
  237.             104  => 'Chamber Music',
  238.             105  => 'Sonata',
  239.             106  => 'Symphony',
  240.             107  => 'Booty Bass',
  241.             108  => 'Primus',
  242.             109  => 'Porn Groove',
  243.             110  => 'Satire',
  244.             111  => 'Slow Jam',
  245.             112  => 'Club',
  246.             113  => 'Tango',
  247.             114  => 'Samba',
  248.             115  => 'Folklore',
  249.             116  => 'Ballad',
  250.             117  => 'Power Ballad',
  251.             118  => 'Rhythmic Soul',
  252.             119  => 'Freestyle',
  253.             120  => 'Duet',
  254.             121  => 'Punk Rock',
  255.             122  => 'Drum Solo',
  256.             123  => 'A Cappella',
  257.             124  => 'Euro-House',
  258.             125  => 'Dance Hall',
  259.             126  => 'Goa',
  260.             127  => 'Drum & Bass',
  261.             128  => 'Club-House',
  262.             129  => 'Hardcore',
  263.             130  => 'Terror',
  264.             131  => 'Indie',
  265.             132  => 'BritPop',
  266.             133  => 'Negerpunk',
  267.             134  => 'Polsk Punk',
  268.             135  => 'Beat',
  269.             136  => 'Christian Gangsta Rap',
  270.             137  => 'Heavy Metal',
  271.             138  => 'Black Metal',
  272.             139  => 'Crossover',
  273.             140  => 'Contemporary Christian',
  274.             141  => 'Christian Rock',
  275.             142  => 'Merengue',
  276.             143  => 'Salsa',
  277.             144  => 'Thrash Metal',
  278.             145  => 'Anime',
  279.             146  => 'JPop',
  280.             147  => 'Synthpop',
  281.  
  282.             255  => 'Unknown',
  283.  
  284.             'CR' => 'Cover',
  285.             'RX' => 'Remix'
  286.         );
  287.  
  288.         static $GenreLookupSCMPX = array();
  289.         if ($allowSCMPXextended && empty($GenreLookupSCMPX)) {
  290.             $GenreLookupSCMPX = $GenreLookup;
  291.             // http://www.geocities.co.jp/SiliconValley-Oakland/3664/alittle.html#GenreExtended
  292.             // Extended ID3v1 genres invented by SCMPX
  293.             // Note that 255 "Japanese Anime" conflicts with standard "Unknown"
  294.             $GenreLookupSCMPX[240] = 'Sacred';
  295.             $GenreLookupSCMPX[241] = 'Northern Europe';
  296.             $GenreLookupSCMPX[242] = 'Irish & Scottish';
  297.             $GenreLookupSCMPX[243] = 'Scotland';
  298.             $GenreLookupSCMPX[244] = 'Ethnic Europe';
  299.             $GenreLookupSCMPX[245] = 'Enka';
  300.             $GenreLookupSCMPX[246] = 'Children\'s Song';
  301.             $GenreLookupSCMPX[247] = 'Japanese Sky';
  302.             $GenreLookupSCMPX[248] = 'Japanese Heavy Rock';
  303.             $GenreLookupSCMPX[249] = 'Japanese Doom Rock';
  304.             $GenreLookupSCMPX[250] = 'Japanese J-POP';
  305.             $GenreLookupSCMPX[251] = 'Japanese Seiyu';
  306.             $GenreLookupSCMPX[252] = 'Japanese Ambient Techno';
  307.             $GenreLookupSCMPX[253] = 'Japanese Moemoe';
  308.             $GenreLookupSCMPX[254] = 'Japanese Tokusatsu';
  309.             //$GenreLookupSCMPX[255] = 'Japanese Anime';
  310.         }
  311.  
  312.         return ($allowSCMPXextended ? $GenreLookupSCMPX : $GenreLookup);
  313.     }
  314.  
  315.     public static function LookupGenreName($genreid, $allowSCMPXextended=true) {
  316.         switch ($genreid) {
  317.             case 'RX':
  318.             case 'CR':
  319.                 break;
  320.             default:
  321.                 if (!is_numeric($genreid)) {
  322.                     return false;
  323.                 }
  324.                 $genreid = intval($genreid); // to handle 3 or '3' or '03'
  325.                 break;
  326.         }
  327.         $GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
  328.         return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false);
  329.     }
  330.  
  331.     public static function LookupGenreID($genre, $allowSCMPXextended=false) {
  332.         $GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
  333.         $LowerCaseNoSpaceSearchTerm = strtolower(str_replace(' ', '', $genre));
  334.         foreach ($GenreLookup as $key => $value) {
  335.             if (strtolower(str_replace(' ', '', $value)) == $LowerCaseNoSpaceSearchTerm) {
  336.                 return $key;
  337.             }
  338.         }
  339.         return false;
  340.     }
  341.  
  342.     public static function StandardiseID3v1GenreName($OriginalGenre) {
  343.         if (($GenreID = self::LookupGenreID($OriginalGenre)) !== false) {
  344.             return self::LookupGenreName($GenreID);
  345.         }
  346.         return $OriginalGenre;
  347.     }
  348.  
  349.     public static function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') {
  350.         $ID3v1Tag  = 'TAG';
  351.         $ID3v1Tag .= str_pad(trim(substr($title,  0, 30)), 30, "\x00", STR_PAD_RIGHT);
  352.         $ID3v1Tag .= str_pad(trim(substr($artist, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
  353.         $ID3v1Tag .= str_pad(trim(substr($album,  0, 30)), 30, "\x00", STR_PAD_RIGHT);
  354.         $ID3v1Tag .= str_pad(trim(substr($year,   0,  4)),  4, "\x00", STR_PAD_LEFT);
  355.         if (!empty($track) && ($track > 0) && ($track <= 255)) {
  356.             $ID3v1Tag .= str_pad(trim(substr($comment, 0, 28)), 28, "\x00", STR_PAD_RIGHT);
  357.             $ID3v1Tag .= "\x00";
  358.             if (gettype($track) == 'string') {
  359.                 $track = (int) $track;
  360.             }
  361.             $ID3v1Tag .= chr($track);
  362.         } else {
  363.             $ID3v1Tag .= str_pad(trim(substr($comment, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
  364.         }
  365.         if (($genreid < 0) || ($genreid > 147)) {
  366.             $genreid = 255; // 'unknown' genre
  367.         }
  368.         switch (gettype($genreid)) {
  369.             case 'string':
  370.             case 'integer':
  371.                 $ID3v1Tag .= chr(intval($genreid));
  372.                 break;
  373.             default:
  374.                 $ID3v1Tag .= chr(255); // 'unknown' genre
  375.                 break;
  376.         }
  377.  
  378.         return $ID3v1Tag;
  379.     }
  380.  
  381. }
  382.