Fix scanning main id3v2 tags

This commit is contained in:
wapmorgan 2017-01-16 02:15:40 +03:00
parent f7bd4f38d8
commit a0a28c6a57
3 changed files with 172 additions and 111 deletions

View File

@ -11,12 +11,12 @@ This class extracts information from mpeg/mp3 audio:
| Audio | id3v1 Tags | id3v2 Tags | | Audio | id3v1 Tags | id3v2 Tags |
|--------------|------------|------------| |--------------|------------|------------|
| duration | song | | | duration | song | TIT2 |
| bitRate | artist | | | bitRate | artist | TPE1 |
| sampleRate | album | | | sampleRate | album | TALB |
| channel | year | | | channel | year | TYER |
| framesCount | comment | | | framesCount | comment | COMM |
| codecVersion | genre | | | codecVersion | genre | TCON |
| layerVersion | | | | layerVersion | | |
1. Usage 1. Usage

View File

@ -20,7 +20,7 @@ function substrIfLonger($string, $maxLength) {
return $string; return $string;
} }
function analyze($filename, &$total_duration, &$total_parse_time) { function analyze($filename, &$total_duration, &$total_parse_time, $id3v2 = false) {
if (!is_readable($filename)) return; if (!is_readable($filename)) return;
try { try {
$audio = new Mp3Info($filename, true); $audio = new Mp3Info($filename, true);
@ -28,6 +28,17 @@ function analyze($filename, &$total_duration, &$total_parse_time) {
return null; return null;
} }
echo sprintf('%15s | %4s | %7s | %0.1fkHz | %-11s | %-10s | %.5f', substrIfLonger(basename($filename), 15), formatTime($audio->duration), $audio->isVbr ? 'vbr' : ($audio->bitRate / 1000).'kbps', ($audio->sampleRate / 1000), isset($audio->tags1['song']) ? substrIfLonger($audio->tags1['song'], 11) : null, isset($audio->tags1['artist']) ? substrIfLonger($audio->tags1['artist'], 10) : null, $audio->_parsingTime).PHP_EOL; echo sprintf('%15s | %4s | %7s | %0.1fkHz | %-11s | %-10s | %.5f', substrIfLonger(basename($filename), 15), formatTime($audio->duration), $audio->isVbr ? 'vbr' : ($audio->bitRate / 1000).'kbps', ($audio->sampleRate / 1000), isset($audio->tags1['song']) ? substrIfLonger($audio->tags1['song'], 11) : null, isset($audio->tags1['artist']) ? substrIfLonger($audio->tags1['artist'], 10) : null, $audio->_parsingTime).PHP_EOL;
if ($id3v2 && !empty($audio->tags2)) {
foreach ($audio->tags2 as $tag=>$value) {
echo ' '.$tag.': ';
if ($tag == 'COMM') {
foreach ($value as $lang => $comment) {
echo '['.$lang.'] '.$comment['short'].'; '.$comment['actual'].PHP_EOL;
}
} else
echo $value.PHP_EOL;
}
}
$total_duration += $audio->duration; $total_duration += $audio->duration;
$total_parse_time += $audio->_parsingTime; $total_parse_time += $audio->_parsingTime;
} }
@ -57,7 +68,7 @@ foreach ($argv as $arg) {
} }
} }
} else if (is_file($arg)) { } else if (is_file($arg)) {
analyze($arg, $total_duration, $total_parse_time); analyze($arg, $total_duration, $total_parse_time, true);
if ($compare) analyzeId3($f, $id3_parse_time); if ($compare) analyzeId3($f, $id3_parse_time);
} }
} }

View File

@ -25,8 +25,8 @@ class Mp3Info {
const VBR_SYNC = "Xing"; const VBR_SYNC = "Xing";
const CBR_SYNC = "Info"; const CBR_SYNC = "Info";
const TAGS = 1; const META = 1;
const META = 2; const TAGS = 2;
const MPEG_1 = 1; const MPEG_1 = 1;
const MPEG_2 = 2; const MPEG_2 = 2;
@ -420,48 +420,84 @@ class Mp3Info {
), ),
); );
switch ($frame_id) { switch ($frame_id) {
case 'UFID': # Unique file identifier // case 'UFID': # Unique file identifier
break; // break;
################# Text information frames ################# Text information frames
case 'TALB': # Album/Movie/Show title case 'TALB': # Album/Movie/Show title
case 'TBPM': # BPM (beats per minute) $raw = fread($fp, $frame_size);
case 'TCOM': # Composer // var_dump($raw);
$data = unpack("C1encoding/A".($frame_size - 1)."information", $raw);
if ((bool)($data['encoding'] == 0x00)) # ISO-8859-1
$this->tags2[$frame_id] = mb_convert_encoding($data['information'], 'utf-8', 'iso-8859-1');
else # utf-16
$this->tags2[$frame_id] = mb_convert_encoding($data['information'], 'utf-8', 'utf-16');
break;
// case 'TBPM': # BPM (beats per minute)
// case 'TCOM': # Composer
case 'TCON': # Content type case 'TCON': # Content type
case 'TCOP': # Copyright message $raw = fread($fp, $frame_size);
case 'TDAT': # Date $data = unpack("C1encoding/A".($frame_size - 1)."information", $raw);
case 'TDLY': # Playlist delay if ((bool)($data['encoding'] == 0x00)) # ISO-8859-1
case 'TENC': # Encoded by $this->tags2[$frame_id] = mb_convert_encoding($data['information'], 'utf-8', 'iso-8859-1');
case 'TEXT': # Lyricist/Text writer else # utf-16
case 'TFLT': # File type $this->tags2[$frame_id] = mb_convert_encoding($data['information'], 'utf-8', 'utf-16');
case 'TIME': # Time break;
case 'TIT1': # Content group description // case 'TCOP': # Copyright message
// case 'TDAT': # Date
// case 'TDLY': # Playlist delay
// case 'TENC': # Encoded by
// case 'TEXT': # Lyricist/Text writer
// case 'TFLT': # File type
// case 'TIME': # Time
// case 'TIT1': # Content group description
case 'TIT2': # Title/songname/content description case 'TIT2': # Title/songname/content description
case 'TIT3': # Subtitle/Description refinement $raw = fread($fp, $frame_size);
case 'TKEY': # Initial key $data = unpack("C1encoding/A".($frame_size - 1)."information", $raw);
case 'TLAN': # Language(s) if ((bool)($data['encoding'] == 0x00)) # ISO-8859-1
case 'TLEN': # Length $this->tags2[$frame_id] = mb_convert_encoding($data['information'], 'utf-8', 'iso-8859-1');
case 'TMED': # Media type else # utf-16
case 'TOAL': # Original album/movie/show title $this->tags2[$frame_id] = mb_convert_encoding($data['information'], 'utf-8', 'utf-16');
case 'TOFN': # Original filename break;
case 'TOLY': # Original lyricist(s)/text writer(s) // case 'TIT3': # Subtitle/Description refinement
case 'TOPE': # Original artist(s)/performer(s) // case 'TKEY': # Initial key
case 'TORY': # Original release year // case 'TLAN': # Language(s)
case 'TOWN': # File owner/licensee // case 'TLEN': # Length
// case 'TMED': # Media type
// case 'TOAL': # Original album/movie/show title
// case 'TOFN': # Original filename
// case 'TOLY': # Original lyricist(s)/text writer(s)
// case 'TOPE': # Original artist(s)/performer(s)
// case 'TORY': # Original release year
// case 'TOWN': # File owner/licensee
case 'TPE1': # Lead performer(s)/Soloist(s) case 'TPE1': # Lead performer(s)/Soloist(s)
case 'TPE2': # Band/orchestra/accompaniment $raw = fread($fp, $frame_size);
case 'TPE3': # Conductor/performer refinement $data = unpack("C1encoding/A".($frame_size - 1)."information", $raw);
case 'TPE4': # Interpreted, remixed, or otherwise modified by if ((bool)($data['encoding'] == 0x00)) # ISO-8859-1
case 'TPOS': # Part of a set $this->tags2[$frame_id] = mb_convert_encoding($data['information'], 'utf-8', 'iso-8859-1');
case 'TPUB': # Publisher else # utf-16
case 'TRCK': # Track number/Position in set $this->tags2[$frame_id] = mb_convert_encoding($data['information'], 'utf-8', 'utf-16');
case 'TRDA': # Recording dates break;
case 'TRSN': # Internet radio station name // case 'TPE2': # Band/orchestra/accompaniment
case 'TRSO': # Internet radio station owner // case 'TPE3': # Conductor/performer refinement
case 'TSIZ': # Size // case 'TPE4': # Interpreted, remixed, or otherwise modified by
case 'TSRC': # ISRC (international standard recording code) // case 'TPOS': # Part of a set
case 'TSSE': # Software/Hardware and settings used for encoding // case 'TPUB': # Publisher
// case 'TRCK': # Track number/Position in set
// case 'TRDA': # Recording dates
// case 'TRSN': # Internet radio station name
// case 'TRSO': # Internet radio station owner
// case 'TSIZ': # Size
// case 'TSRC': # ISRC (international standard recording code)
// case 'TSSE': # Software/Hardware and settings used for encoding
case 'TYER': # Year case 'TYER': # Year
$raw = fread($fp, $frame_size);
$data = unpack("C1encoding/A".($frame_size - 1)."information", $raw);
if ((bool)($data['encoding'] == 0x00)) # ISO-8859-1
$this->tags2[$frame_id] = mb_convert_encoding($data['information'], 'utf-8', 'iso-8859-1');
else # utf-16
$this->tags2[$frame_id] = mb_convert_encoding($data['information'], 'utf-8', 'utf-16');
break;
case 'TXXX': # User defined text information frame case 'TXXX': # User defined text information frame
$raw = fread($fp, $frame_size); $raw = fread($fp, $frame_size);
$data = unpack("C1encoding/A".($frame_size - 1)."information", $raw); $data = unpack("C1encoding/A".($frame_size - 1)."information", $raw);
@ -473,53 +509,64 @@ class Mp3Info {
################# Text information frames ################# Text information frames
################# URL link frames ################# URL link frames
case 'WCOM': # Commercial information // case 'WCOM': # Commercial information
break; // break;
case 'WCOP': # Copyright/Legal information // case 'WCOP': # Copyright/Legal information
break; // break;
case 'WOAF': # Official audio file webpage // case 'WOAF': # Official audio file webpage
break; // break;
case 'WOAR': # Official artist/performer webpage // case 'WOAR': # Official artist/performer webpage
break; // break;
case 'WOAS': # Official audio source webpage // case 'WOAS': # Official audio source webpage
break; // break;
case 'WORS': # Official internet radio station homepage // case 'WORS': # Official internet radio station homepage
break; // break;
case 'WPAY': # Payment // case 'WPAY': # Payment
break; // break;
case 'WPUB': # Publishers official webpage // case 'WPUB': # Publishers official webpage
break; // break;
case 'WXXX': # User defined URL link frame // case 'WXXX': # User defined URL link frame
break; // break;
################# URL link frames ################# URL link frames
case 'IPLS': # Involved people list // case 'IPLS': # Involved people list
break; // break;
case 'MCDI': # Music CD identifier // case 'MCDI': # Music CD identifier
break; // break;
case 'ETCO': # Event timing codes // case 'ETCO': # Event timing codes
break; // break;
case 'MLLT': # MPEG location lookup table // case 'MLLT': # MPEG location lookup table
break; // break;
case 'SYTC': # Synchronized tempo codes // case 'SYTC': # Synchronized tempo codes
break; // break;
case 'USLT': # Unsychronized lyric/text transcription // case 'USLT': # Unsychronized lyric/text transcription
break; // break;
case 'SYLT': # Synchronized lyric/text // case 'SYLT': # Synchronized lyric/text
break; // break;
case 'COMM': # Comments case 'COMM': # Comments
$dataEnd = ftell($fp) + $frame_size; $dataEnd = ftell($fp) + $frame_size;
$raw = fread($fp, 4); $raw = fread($fp, 4);
$data = unpack("C1encoding/A3language", $raw); $data = unpack("C1encoding/A3language", $raw);
// read until \null character // read until \null character
$short_description = null; $short_description = null;
$last_null = false;
$actual_text = false;
while (ftell($fp) < $dataEnd) { while (ftell($fp) < $dataEnd) {
$char = fgetc($fp); $char = fgetc($fp);
if ($char == chr(0)) $actual_text = null; if ($char == "\00" && $actual_text === false) {
else if (isset($actual_text)) $actual_text .= $char; if ($data['encoding'] == 0x1) { # two null-bytes for utf-16
if ($last_null)
$actual_text = null;
else
$last_null = true;
} else # no condition for iso-8859-1
$actual_text = null;
}
else if ($actual_text !== false) $actual_text .= $char;
else $short_description .= $char; else $short_description .= $char;
} }
if (!isset($actual_text)) $actual_text = $short_description; if ($actual_text === false) $actual_text = $short_description;
// list($short_description, $actual_text) = sscanf("s".chr(0)."s", $data['texts']); // list($short_description, $actual_text) = sscanf("s".chr(0)."s", $data['texts']);
// list($short_description, $actual_text) = explode(chr(0), $data['texts']); // list($short_description, $actual_text) = explode(chr(0), $data['texts']);
$this->tags2[$frame_id][$data['language']] = array( $this->tags2[$frame_id][$data['language']] = array(
@ -527,42 +574,45 @@ class Mp3Info {
'actual' => (bool)($data['encoding'] == 0x00) ? mb_convert_encoding($actual_text, 'utf-8', 'iso-8859-1') : mb_convert_encoding($actual_text, 'utf-8', 'utf-16'), 'actual' => (bool)($data['encoding'] == 0x00) ? mb_convert_encoding($actual_text, 'utf-8', 'iso-8859-1') : mb_convert_encoding($actual_text, 'utf-8', 'utf-16'),
); );
break; break;
case 'RVAD': # Relative volume adjustment // case 'RVAD': # Relative volume adjustment
break; // break;
case 'EQUA': # Equalization // case 'EQUA': # Equalization
break; // break;
case 'RVRB': # Reverb // case 'RVRB': # Reverb
break; // break;
case 'APIC': # Attached picture // case 'APIC': # Attached picture
break; // break;
case 'GEOB': # General encapsulated object // case 'GEOB': # General encapsulated object
break; // break;
case 'PCNT': # Play counter case 'PCNT': # Play counter
$raw = fread($fp, $frame_size); $raw = fread($fp, $frame_size);
$data = unpack("L", $raw); $data = unpack("L", $raw);
$this->tags2[$frame_id] = $data[1]; $this->tags2[$frame_id] = $data[1];
break; break;
case 'POPM': # Popularimeter // case 'POPM': # Popularimeter
break; // break;
case 'RBUF': # Recommended buffer size // case 'RBUF': # Recommended buffer size
break; // break;
case 'AENC': # Audio encryption // case 'AENC': # Audio encryption
break; // break;
case 'LINK': # Linked information // case 'LINK': # Linked information
break; // break;
case 'POSS': # Position synchronisation frame // case 'POSS': # Position synchronisation frame
break; // break;
case 'USER': # Terms of use // case 'USER': # Terms of use
break; // break;
case 'OWNE': # Ownership frame // case 'OWNE': # Ownership frame
break; // break;
case 'COMR': # Commercial frame // case 'COMR': # Commercial frame
break; // break;
case 'ENCR': # Encryption method registration // case 'ENCR': # Encryption method registration
break; // break;
case 'GRID': # Group identification registration // case 'GRID': # Group identification registration
break; // break;
case 'PRIV': # Private frame // case 'PRIV': # Private frame
// break;
default:
fseek($fp, $frame_size, SEEK_CUR);
break; break;
} }
} }