The text below is selected, press Ctrl+C to copy to your clipboard. (⌘+C on Mac) No line numbers will be copied.
Guest
Reader
By Guest on 8th May 2018 03:07:08 PM | Syntax: PHP | Views: 75



New paste | Download | Show/Hide line no. | Copy text to clipboard
  1. <?php
  2. namespace brokencube\ID3;
  3.  
  4. class Reader
  5. {
  6.     /*
  7.         http://id3.org/id3v2.3.0
  8.     */
  9.    
  10.     protected $filename;
  11.     protected $fp;
  12.     protected $data;
  13.     public function __construct($filename)
  14.     {
  15.         $this->filename = $filename;
  16.     }
  17.  
  18.     protected function unpackSyncSafeInteger($bytes)
  19.     {
  20.         list(,$size) = unpack('N', $bytes);
  21.  
  22.         return ($size & 0x0000007F)
  23.             + (($size & 0x00007F00) >> 1)
  24.             + (($size & 0x007F0000) >> 2)
  25.             + (($size & 0x7F000000) >> 3);
  26.     }
  27.  
  28.     public function view()
  29.     {
  30.         $this->fp = fopen($this->filename,'r');
  31.        
  32.         $mainHeader = fread($this->fp, 10);
  33.         // Check we have the expected magic string
  34.         if (substr($mainHeader,0,3) != 'ID3') {
  35.             return [];
  36.         }
  37.        
  38.         $mainHeaderLength = $this->unpackSyncSafeInteger(substr($mainHeader, 6, 4));
  39.        
  40.         // Sanity check - if the decoded length value is larger than the file, we must be looking at junk data, bail out.
  41.         if ($mainHeaderLength > filesize($this->filename)) {
  42.             return [];
  43.         }
  44.  
  45.         if ($mainHeaderLength < 10) {
  46.             return [];
  47.         }
  48.        
  49.         $versionNumber = ord(substr($mainHeader, 3, 1));
  50.         $pointer = 10;
  51.        
  52.         $mainFlags = substr($mainHeader, 5, 1);
  53.         $unsync = ord($mainFlags) & 0x80;
  54.        
  55.         if ($unsync) {
  56.             $data = fread($this->fp, $mainHeaderLength * 1.05);
  57.             fclose($this->fp);
  58.             $this->fp = fopen('php://temp/maxmemory:'. (4*1024), 'w+');
  59.             fwrite($this->fp, $mainHeader . $this->unsynchroniseString($data));
  60.         }
  61.        
  62.         switch ($versionNumber) {
  63.             case 2:
  64.                 return $this->decodeV2($mainHeaderLength, $pointer, $unsync);
  65.            
  66.             case 3:
  67.                 return $this->decodeV3($mainHeaderLength, $pointer, $unsync);
  68.            
  69.             case 4:
  70.                 return $this->decodeV4($mainHeaderLength, $pointer, $unsync);
  71.         }
  72.     }
  73.    
  74.     public function decodeV2($length, $pointer = 10, $unsync)
  75.     {
  76.         $tags = [];
  77.         do {
  78.             $tagEncoding = null;
  79.             $tagDataExtra = null;
  80.             $tagLang = null;
  81.            
  82.             fseek($this->fp, $pointer);
  83.            
  84.             // Read data for next tag
  85.             $tagName = fread($this->fp, 3);
  86.             $tagRawSize = fread($this->fp, 3);
  87.             list(,$tagDataSize) = unpack('N', chr(0) . $tagRawSize);
  88.             $data = $tagDataSize ? fread($this->fp, $tagDataSize) : '';
  89.            
  90.             // If we somehow end up with a dead resource (or stream past the end of the file etc.)
  91.             if ($tagName === false) {
  92.                 return $tags;
  93.             }
  94.            
  95.             // Have we hit the end padding?
  96.             if (bin2hex($tagName) === '000000') {
  97.                 return $tags;
  98.             }
  99.  
  100.             if ($unsync) {
  101.                 $data = $this->unsynchroniseString($data);
  102.             }
  103.  
  104.             // Trim any nul chars off the data
  105.             $data = $this->trimNull($data);
  106.            
  107.             // Do special things for the Com tag
  108.             switch (true) {
  109.                 case $tagName == "COM":
  110.                     $tagEncoding = ord(substr($data, 0, 1));
  111.                     $tagLang = substr($data, 1, 3);
  112.                     $data = $this->trimNull(substr($data, 4));
  113.                     $tagDataExtra = $this->trimNull(substr($data, 0, strpos($data, chr(0))));
  114.                     $tagData = substr($data, strpos($data, chr(0)));
  115.                     $tagData = $this->decodeText($tagData, $tagEncoding);
  116.                     break;
  117.                
  118.                 case substr($tagName, 0, 1) == 'T':
  119.                     $tagEncoding = ord(substr($data, 0, 1));
  120.                     $tagData = $this->decodeText(substr($data, 1), $tagEncoding);
  121.                     break;
  122.                
  123.                 case $tagName == "TXX":
  124.                     $tagEncoding = ord(substr($data, 0, 1));
  125.                     $data = $this->trimNull(substr($data, 1));
  126.                     $tagDataExtra = $this->trimNull(substr($data, 0, strpos($data, chr(0))));
  127.                     $tagData = substr($data, strpos($data, chr(0)));
  128.                     $tagData = $this->decodeText($tagData, $tagEncoding);
  129.                     break;
  130.                
  131.                 default:
  132.                     $tagData = $data;
  133.                     $tagLang = null;
  134.                     break;
  135.             }
  136.  
  137.             $tags[] = [
  138.                 'tagName' => $tagName,
  139.                 'tagData' => $tagData,
  140.                 'tagLang' => $tagLang,
  141.                 'tagDataExtra' => $tagDataExtra                
  142.             ];
  143.            
  144.             $pointer += (6 + $tagDataSize);
  145.            
  146.         } while ($pointer < $length);
  147.         return $tags;
  148.     }
  149.        
  150.     public function decodeV3($length, $pointer, $unsync)
  151.     {
  152.         $tags = [];
  153.         do {
  154.             $tagEncoding = null;
  155.             $tagDataExtra = null;
  156.             $tagLang = null;
  157.            
  158.             fseek($this->fp, $pointer);
  159.            
  160.             // Read data for next tag
  161.             $tagName = fread($this->fp, 4);
  162.             $tagRawSize = fread($this->fp, 4);
  163.             $tagStatusFlags = fread($this->fp, 1);
  164.             $tagFormatFlags = fread($this->fp, 1);
  165.             list(,$tagDataSize) = unpack('N', $tagRawSize);
  166.            
  167.             // If we somehow end up with a dead resource (or stream past the end of the file etc.)
  168.             if ($tagName === false) {
  169.                 return $tags;
  170.             }
  171.            
  172.             // Have we hit the end padding?
  173.             if (bin2hex($tagName) === '00000000') {
  174.                 return $tags;
  175.             }
  176.            
  177.             $data = $tagDataSize ? fread($this->fp, $tagDataSize) : '';
  178.            
  179.             // Decode Flags
  180.             $frameFlags = $this->version3Frame($tagStatusFlags, $tagFormatFlags);
  181.            
  182.             // If we have a secondary 32bit length, strip it.
  183.             if ($frameFlags['length'] == true){
  184.                 $data = substr($data, 4);
  185.             }
  186.  
  187.             if ($frameFlags['unsync'] || $unsync) {
  188.                 $data = $this->unsynchroniseString($data);
  189.             }
  190.            
  191.             // Special Decoding for specific tags
  192.             switch (true) {
  193.                 case $tagName == 'APIC':
  194.                     $tagData = $this->imageData($data);
  195.                     break;
  196.  
  197.                 case $tagName == "TXXX":
  198.                     $tagEncoding = ord(substr($data, 0, 1));
  199.                     $data = $this->trimNull(substr($data, 1));
  200.                     $tagDataExtra = $this->trimNull(substr($data, 0, strpos($data, chr(0))));
  201.                     $tagData = substr($data, strpos($data, chr(0)));
  202.                     $tagData = $this->decodeText($tagData, $tagEncoding);
  203.                     break;
  204.                
  205.                 case $tagName == "COMM":
  206.                     // Remove the text encoding byte
  207.                     $tagEncoding = ord(substr($data, 0, 1));
  208.                     $tagLang =  $this->trimNull(substr($data, 1, 3));
  209.                     $tagData = $this->decodeText(substr($data, 4), $tagEncoding);
  210.                     break;
  211.                
  212.                 case substr($tagName, 0, 1) == 'T':
  213.                 case substr($tagName, 0, 1) == 'W':
  214.                     $tagEncoding = ord(substr($data, 0, 1));
  215.                     $tagData = $this->decodeText(substr($data, 1), $tagEncoding);
  216.                     break;
  217.                
  218.                 default:
  219.                     $tagData = $data;
  220.                     break;
  221.             }
  222.            
  223.             $tags[] = [
  224.                 'tagName' => $tagName,
  225.                 'tagData' => $tagData,
  226.                 'tagLang' => $tagLang,
  227.                 'tagDataExtra' => $tagDataExtra
  228.             ];
  229.            
  230.             $pointer += (10 + $tagDataSize);
  231.            
  232.         } while ($pointer < $length);
  233.         return $tags;
  234.     }
  235.    
  236.     public function decodeV4($length, $pointer, $unsync)
  237.     {
  238.         $tags = [];
  239.         do {
  240.             $tagEncoding = null;
  241.             $tagDataExtra = null;
  242.             $tagLang = null;
  243.            
  244.             fseek($this->fp, $pointer);
  245.            
  246.             // Read data for next tag
  247.             $tagName = fread($this->fp, 4);
  248.             $tagRawSize = fread($this->fp, 4);
  249.             $tagStatusFlags = fread($this->fp, 1);
  250.             $tagFormatFlags = fread($this->fp, 1);
  251.             $tagDataSize = $this->unpackSyncSafeInteger($tagRawSize);
  252.            
  253.             // If we somehow end up with a dead resource (or stream past the end of the file etc.)
  254.             if ($tagName === false) {
  255.                 return $tags;
  256.             }
  257.            
  258.             // Have we hit the end padding?
  259.             if (bin2hex($tagName) === '00000000') {
  260.                 return $tags;
  261.             }
  262.  
  263.             $data = $tagDataSize ? fread($this->fp, $tagDataSize) : '';
  264.            
  265.            
  266.             // Decode Flags
  267.             $frameFlags = $this->version4Frame($tagStatusFlags, $tagFormatFlags);
  268.            
  269.             // If we have a secondary 32bit length, strip it.
  270.             if ($frameFlags['length'] == true){
  271.                 $data = substr($data, 4);
  272.             }
  273.            
  274.             if ($frameFlags['unsync'] || $unsync) {
  275.                 $data = $this->unsynchroniseString($data);
  276.             }
  277.            
  278.             // Special Decoding for specific tags
  279.             switch (true) {
  280.                 case $tagName == 'APIC':
  281.                     $tagData = $this->imageData($data);
  282.                     break;
  283.  
  284.                 case $tagName == "TXXX":
  285.                     $tagEncoding = ord(substr($data, 0, 1));
  286.                     $data = $this->trimNull(substr($data, 1));
  287.                     $tagDataExtra = $this->trimNull(substr($data, 0, strpos($data, chr(0))));
  288.                     $tagData = substr($data, strpos($data, chr(0)));
  289.                     $tagData = $this->decodeText($tagData, $tagEncoding);
  290.                     break;
  291.                
  292.                 case $tagName == "COMM":
  293.                     // Remove the text encoding byte
  294.                     $tagEncoding = ord(substr($data, 0, 1));
  295.                     $tagLang =  $this->trimNull(substr($data, 1, 3));
  296.                     $tagData = $this->decodeText(substr($data, 4), $tagEncoding);
  297.                     break;
  298.                
  299.                 case substr($tagName, 0, 1) == 'T':
  300.                 case substr($tagName, 0, 1) == 'W':
  301.                     $tagEncoding = ord(substr($data, 0, 1));
  302.                     $tagData = $this->decodeText(substr($data, 1), $tagEncoding);
  303.                     break;
  304.                
  305.                 default:
  306.                     $tagData = $data;
  307.                     break;
  308.             }
  309.            
  310.             $tags[] = [
  311.                 'tagName' => $tagName,
  312.                 'tagData' => $tagData,
  313.                 'tagLang' => $tagLang,
  314.                 'tagDataExtra' => $tagDataExtra
  315.             ];
  316.            
  317.             $pointer += (10 + $tagDataSize);
  318.            
  319.         } while ($pointer < $length);
  320.         return $tags;
  321.     }
  322.    
  323.     public function decodeText($data, $encoding)
  324.     {
  325.         if ($encoding == 1 or $encoding == 2) {
  326.             $data = mb_convert_encoding($data, 'UTF-8' , 'UTF-16');
  327.         }
  328.         return $this->trimNull($data);
  329.     }
  330.  
  331.     protected function imageData($data)
  332.     {
  333.         // [FIXME] Clean this when I have more brain power
  334.         $position = 0;
  335.         $image['text_encoding'] = ord(substr($data, 0, 1));
  336.         $mime = substr($data, 1);
  337.         $position = stripos($mime, null);
  338.         $image['MIME'] = substr($mime, 0, $position);
  339.         $image['picture_type'] = substr($mime, $position, 1);
  340.         $position += 1;
  341.         $description = substr($mime, $position);
  342.         $descPosition = stripos($description, null);
  343.         $image['description'] = substr($description, $position, $descPosition);
  344.         $image['image'] = $this->trimNull(substr($description, $descPosition));
  345.         #echo "<img src='data:image/jpeg;base64,".base64_encode($image['image'])."'/>";
  346.        return $image;
  347.  
  348.     }
  349.  
  350.     protected function unsynchroniseString($string)
  351.     {
  352.         return str_replace(chr(255) . chr(0), chr(255), $string);
  353.     }
  354.  
  355.     protected function version3Frame($frameStatus, $frameFormat)
  356.     {
  357.         return [
  358.             'tag' =>         bin2hex($frameStatus) & 0b10000000,
  359.             'file' =>        bin2hex($frameStatus) & 0b01000000,
  360.             'read-only' =>   bin2hex($frameStatus) & 0b00100000,
  361.            
  362.             'grouping' =>    bin2hex($frameFormat) & 0b00100000,
  363.             'compression' => bin2hex($frameFormat) & 0b10000000,
  364.             'encrypt' =>     bin2hex($frameFormat) & 0b01000000,
  365.             'unsync' => false,
  366.             'length' => false,
  367.         ];
  368.     }
  369.    
  370.     protected function version4Frame($frameStatus, $frameFormat)
  371.     {
  372.         return [
  373.             'tag' =>         bin2hex($frameStatus) & 0b01000000,
  374.             'file' =>        bin2hex($frameStatus) & 0b00100000,
  375.             'read-only' =>   bin2hex($frameStatus) & 0b00010000,
  376.            
  377.             'grouping' =>    bin2hex($frameFormat) & 0b01000000,
  378.             'compression' => bin2hex($frameFormat) & 0b00001000,
  379.             'encrypt' =>     bin2hex($frameFormat) & 0b00000100,
  380.             'unsync' =>      bin2hex($frameFormat) & 0b00000010,
  381.             'length' =>      bin2hex($frameFormat) & 0b00000001,
  382.         ];
  383.     }
  384.    
  385.     public function trimNull($data)
  386.     {
  387.         return trim($data, chr(0));
  388.     }
  389. }



  • Recent Pastes