├── .gitignore ├── Tests ├── TestFiles │ ├── thumb.jpeg │ └── Exodus - 06 - Piranha.mp3 ├── bootstrap.php └── Id3TagsReaderTest.php ├── composer.json ├── phpunit.xml.dist ├── LICENSE ├── README.md └── PhpId3 ├── Id3TagsReader.php ├── BinaryFileReader.php └── Id3Tags.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /nbproject/private/ -------------------------------------------------------------------------------- /Tests/TestFiles/thumb.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shubhamjain/PHP-ID3/HEAD/Tests/TestFiles/thumb.jpeg -------------------------------------------------------------------------------- /Tests/TestFiles/Exodus - 06 - Piranha.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shubhamjain/PHP-ID3/HEAD/Tests/TestFiles/Exodus - 06 - Piranha.mp3 -------------------------------------------------------------------------------- /Tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | =5.3.3" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./Tests 6 | 7 | 8 | 9 | 10 | ./ 11 | 12 | ./Resources 13 | ./Tests 14 | ./vendor 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Shubham Jain 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Tests/Id3TagsReaderTest.php: -------------------------------------------------------------------------------- 1 | id3 = new Id3TagsReader(fopen(__DIR__ . $this->mp3File, "rb")); 18 | $this->id3->readAllTags(); 19 | } 20 | 21 | public function testGetImage() 22 | { 23 | $image = $this->id3->getImage(); 24 | 25 | $this->assertEquals("mage/jpg", $image[0]); 26 | $this->assertEquals(file_get_contents(__DIR__ . $this->albumCover), $image[1]); 27 | } 28 | 29 | public function testGetId3TagsArray() 30 | { 31 | $id3Tags = $this->id3->getId3Array(); 32 | 33 | $this->assertEquals($id3Tags["TIT2"]["body"], 'Piranha'); 34 | $this->assertEquals($id3Tags["TRCK"]["body"], '6'); 35 | $this->assertEquals($id3Tags["TCON"]["body"], 'Heavy Metal'); 36 | $this->assertEquals($id3Tags["TALB"]["body"], 'Bonded by Blood'); 37 | $this->assertEquals($id3Tags["TYER"]["body"], '1985'); 38 | $this->assertEquals($id3Tags["TPE1"]["body"], 'Exodus'); 39 | $this->assertEquals($id3Tags["TLEN"]["body"], '228963'); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #PHP-ID3 2 | 3 | PHP-ID3 makes use of native PHP to read [ID3 Tags](http://en.wikipedia.org/wiki/ID3‎) and thumbnail from a MP3 file. There have been many revisions to ID3 Tags specification; this program makes use of v3.2 of the [spec](http://id3.org/id3v2.3.0). 4 | 5 | To read binary data more effectively, I have created a sclass, [BinaryFileReader](https://gist.github.com/shubhamjain/5964350), which reads data in named chunks. 6 | 7 | ##How to Install 8 | 9 | Into your composer.json 10 | 11 | ```json 12 | { 13 | "require" : { 14 | "shubhamjain/php-id3": "dev-master" 15 | } 16 | } 17 | ``` 18 | 19 | ##How to Use 20 | 21 | You will first need to include the autoload.php generated by composer and then you can use the classes in PhpId3 namespace. 22 | 23 | ```php 24 | readAllTags(); //Calling this is necesarry before others 35 | 36 | foreach($id3->getId3Array() as $key => $value) { 37 | if( $key !== "APIC" ) { //Skip Image data 38 | echo $value["FullTagName"] . ": " . $value["Body"] . "
"; 39 | } 40 | } 41 | 42 | list($mimeType, $image) = $id3->getImage(); 43 | 44 | file_put_contents("thumb.jpeg", $image ); //Note the image type depends upon MimeType 45 | 46 | //... 47 | ``` 48 | 49 | ##LICENSE 50 | 51 | See ``LICENSE`` for more informations 52 | 53 | ##Feedback 54 | 55 | If you used this project or liked it or have any doubt about the source, send your valuable thoughts at . 56 | -------------------------------------------------------------------------------- /PhpId3/Id3TagsReader.php: -------------------------------------------------------------------------------- 1 | 12 | * @license MIT License 13 | */ 14 | class Id3TagsReader 15 | { 16 | 17 | private $fileReader; 18 | private $id3Array; 19 | private $validMp3 = TRUE; 20 | 21 | public function __construct($fileHandle) 22 | { 23 | $this->fileReader = new BinaryFileReader($fileHandle, array( 24 | "id3" => array(BinaryFileReader::FIXED, 3), 25 | "version" => array(BinaryFileReader::FIXED, 2), 26 | "flag" => array(BinaryFileReader::FIXED, 1), 27 | "sizeTag" => array(BinaryFileReader::FIXED, 4, BinaryFileReader::INT), 28 | )); 29 | 30 | $data = $this->fileReader->read(); 31 | 32 | if( $data->id3 !== "ID3") 33 | { 34 | throw new \Exception("The MP3 file contains no valid ID3 Tags."); 35 | $this->validMp3 = FALSE; 36 | } 37 | 38 | } 39 | 40 | public function readAllTags() 41 | { 42 | assert( $this->validMp3 === TRUE); 43 | 44 | $bytesPos = 10; //From headers 45 | 46 | $this->fileReader->setMap(array( 47 | "frameId" => array(BinaryFileReader::FIXED, 4), 48 | "size" => array(BinaryFileReader::FIXED, 4, BinaryFileReader::INT), 49 | "flag" => array(BinaryFileReader::FIXED, 2), 50 | "body" => array(BinaryFileReader::SIZE_OF, "size"), 51 | )); 52 | 53 | $id3Tags = Id3Tags::getId3Tags(); 54 | 55 | while (($file_data = $this->fileReader->read())) { 56 | 57 | if (!in_array($file_data->frameId, array_keys($id3Tags))) { 58 | break; 59 | } 60 | 61 | $body = $file_data->body; 62 | 63 | // If frame is a text frame then we have to consider 64 | // encoding as shown in spec section 4.2 65 | if( $file_data->frameId[0] === "T" ) 66 | { 67 | // First character determines the encoding, 1 = ISO-8859-1, 0 = UTF - 16 68 | if( intval(bin2hex($body[0]), 16) === 1) 69 | $body = mb_convert_encoding(substr($body, 1), 'UTF-8', 'UTF-16'); // Convert UTF-16 to UTF-8 to compatible with current browsers 70 | } 71 | 72 | $this->id3Array[$file_data->frameId] = array( 73 | "fullTagName" => $id3Tags[$file_data->frameId], 74 | "position" => $bytesPos, 75 | "size" => $file_data->size, 76 | "body" => $body, 77 | ); 78 | 79 | $bytesPos += 4 + 4 + 2 + $file_data->size; 80 | } 81 | return $this; 82 | } 83 | 84 | public function getId3Array() 85 | { 86 | return $this->id3Array; 87 | } 88 | 89 | public function getImage() 90 | { 91 | $fp = fopen('data://text/plain;base64,' . base64_encode($this->id3Array["APIC"]["body"]), 'rb'); //Create an artificial stream from Image data 92 | 93 | $fileReader = new BinaryFileReader($fp, array( 94 | "textEncoding" => array(BinaryFileReader::FIXED, 1), 95 | "mimeType" => array(BinaryFileReader::NULL_TERMINATED), 96 | "fileName" => array(BinaryFileReader::NULL_TERMINATED), 97 | "contentDesc" => array(BinaryFileReader::NULL_TERMINATED), 98 | "binaryData" => array(BinaryFileReader::EOF_TERMINATED) 99 | ) 100 | ); 101 | 102 | $imageData = $fileReader->read(); 103 | 104 | return array($imageData->mimeType, $imageData->binaryData); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /PhpId3/BinaryFileReader.php: -------------------------------------------------------------------------------- 1 | 11 | * @example https://github.com/shubhamjain/PHP-ID3 12 | * @license MIT License 13 | */ 14 | class BinaryFileReader 15 | { 16 | /** 17 | * size of block depends upon the variable defined in the next array element. 18 | */ 19 | const SIZE_OF = 1; 20 | 21 | /** 22 | * Block is read until NULL is encountered. 23 | */ 24 | const NULL_TERMINATED = 2; 25 | 26 | /** 27 | * Block is read until EOF is encountered. 28 | */ 29 | const EOF_TERMINATED = 3; 30 | 31 | /** 32 | * Block size is fixed. 33 | */ 34 | const FIXED = 4; 35 | 36 | /** 37 | * Datatypes to transform the read block 38 | */ 39 | const INT = 5; 40 | 41 | const FLOAT = 6; 42 | 43 | /** 44 | * file handle to read data 45 | */ 46 | private $fp; 47 | 48 | /** 49 | * Associative array of Varaibles and their info ( TYPE, SIZE, DATA_TYPE) 50 | * In special cases it can be an array to handle different types of block data lengths 51 | */ 52 | private $map; 53 | 54 | public function __construct($fp, array $map) 55 | { 56 | $this->fp = $fp; 57 | $this->setMap($map); 58 | } 59 | 60 | public function setMap($map) 61 | { 62 | $this->map = $map; 63 | 64 | foreach ($this->map as $key => $size) { 65 | //Create property from keys of $map 66 | $this->$key = null; 67 | } 68 | } 69 | 70 | public function read() 71 | { 72 | if (feof($this->fp)) { 73 | return false; 74 | } 75 | 76 | foreach ($this->map as $key => $info) { 77 | 78 | $this->fillTag($info, $key); 79 | 80 | if (isset($info[2])) { 81 | $this->convertBinToNumeric($info[2], $key); 82 | } 83 | $this->$key = ltrim($this->$key, "\0x"); 84 | } 85 | return $this; 86 | } 87 | 88 | private function nullTeminated($key) 89 | { 90 | while ((int) bin2hex(($ch = fgetc($this->fp))) !== 0) { 91 | $this->$key .= $ch; 92 | } 93 | } 94 | 95 | private function eofTerminated($key) 96 | { 97 | while (!feof($this->fp)) { 98 | $this->$key .= fgetc($this->fp); 99 | } 100 | } 101 | 102 | private function fillTag($tag, $key) 103 | { 104 | switch ($tag[0]) { 105 | case self::NULL_TERMINATED: 106 | $this->nullTeminated($key); 107 | break; 108 | case self::EOF_TERMINATED: 109 | $this->eofTerminated($key); 110 | break; 111 | case self::SIZE_OF: 112 | //If the variable is not an integer return false 113 | if (!( $tag[1] = $this->$tag[1] )) { 114 | return false; 115 | } 116 | default: 117 | //Read as string 118 | $this->$key = fread($this->fp, $tag[1]); 119 | break; 120 | } 121 | } 122 | 123 | private function convertBinToNumeric($value, $key) 124 | { 125 | switch ($value) { 126 | case self::INT: 127 | $this->$key = intval(bin2hex($this->$key), 16); 128 | break; 129 | case self::FLOAT: 130 | $this->$key = floatval(bin2hex($this->$key), 16); 131 | break; 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /PhpId3/Id3Tags.php: -------------------------------------------------------------------------------- 1 | "Audio encryption", 11 | "APIC" => "Attached picture", 12 | "COMM" => "Comments", 13 | "COMR" => "Commercial frame", 14 | "ENCR" => "Encryption method registration", 15 | "EQUA" => "Equalization", 16 | "ETCO" => "Event timing codes", 17 | "GEOB" => "General encapsulated object", 18 | "GRID" => "Group identification registration", 19 | "IPLS" => "Involved people list", 20 | "LINK" => "Linked information", 21 | "MCDI" => "Music CD identifier", 22 | "MLLT" => "MPEG location lookup table", 23 | "OWNE" => "Ownership frame", 24 | "PRIV" => "Private frame", 25 | "PCNT" => "Play counter", 26 | "POPM" => "Popularimeter", 27 | "POSS" => "Position synchronisation frame", 28 | "RBUF" => "Recommended buffer size", 29 | "RVAD" => "Relative volume adjustment", 30 | "RVRB" => "Reverb", 31 | "SYLT" => "Synchronized lyric/text", 32 | "SYTC" => "Synchronized tempo codes", 33 | "TALB" => "Album/Movie/Show title", 34 | "TBPM" => "BPM (beats per minute)", 35 | "TCOM" => "Composer", 36 | "TCON" => "Content type", 37 | "TCOP" => "Copyright message", 38 | "TDAT" => "Date", 39 | "TDLY" => "Playlist delay", 40 | "TENC" => "Encoded by", 41 | "TEXT" => "Lyricist/Text writer", 42 | "TFLT" => "File type", 43 | "TIME" => "Time", 44 | "TIT1" => "Content group description", 45 | "TIT2" => "Title/songname/content description", 46 | "TIT3" => "Subtitle/Description refinement", 47 | "TKEY" => "Initial key", 48 | "TLAN" => "Language(s)", 49 | "TLEN" => "Length", 50 | "TMED" => "Media type", 51 | "TOAL" => "Original album/movie/show title", 52 | "TOFN" => "Original filename", 53 | "TOLY" => "Original lyricist(s)/text writer(s)", 54 | "TOPE" => "Original artist(s)/performer(s)", 55 | "TORY" => "Original release year", 56 | "TOWN" => "File owner/licensee", 57 | "TPE1" => "Lead performer(s)/Soloist(s)", 58 | "TPE2" => "Band/orchestra/accompaniment", 59 | "TPE3" => "Conductor/performer refinement", 60 | "TPE4" => "Interpreted, remixed, or otherwise modified by", 61 | "TPOS" => "Part of a set", 62 | "TPUB" => "Publisher", 63 | "TRCK" => "Track number/Position in set", 64 | "TRDA" => "Recording dates", 65 | "TRSN" => "Internet radio station name", 66 | "TRSO" => "Internet radio station owner", 67 | "TSIZ" => "Size", 68 | "TSRC" => "ISRC (international standard recording code)", 69 | "TSSE" => "Software/Hardware and settings used for encoding", 70 | "TYER" => "Year", 71 | "TXXX" => "User defined text information frame", 72 | "UFID" => "Unique file identifier", 73 | "USER" => "Terms of use", 74 | "USLT" => "Unsychronized lyric/text transcription", 75 | "WCOM" => "Commercial information", 76 | "WCOP" => "Copyright/Legal information", 77 | "WOAF" => "Official audio file webpage", 78 | "WOAR" => "Official artist/performer webpage", 79 | "WOAS" => "Official audio source webpage", 80 | "WORS" => "Official internet radio station homepage", 81 | "WPAY" => "Payment", 82 | "WPUB" => "Publishers official webpage", 83 | "WXXX" => "User defined URL link frame", 84 | ); 85 | } 86 | } 87 | --------------------------------------------------------------------------------