123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
<?php
if (!defined('GETID3_INCLUDEPATH')) {
exit;
}
class getid3_dsdiff extends getid3_handler
{
public function Analyze() {
$info = &$this->getid3->info;
$this->fseek($info['avdataoffset']);
$DSDIFFheader = $this->fread(4);
if (substr($DSDIFFheader, 0, 4) != 'FRM8') {
$this->error('Expecting "FRM8" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($DSDIFFheader, 0, 4)).'"');
return false;
}
unset($DSDIFFheader);
$this->fseek($info['avdataoffset']);
$info['encoding'] = 'ISO-8859-1';
$info['fileformat'] = 'dsdiff';
$info['mime_type'] = 'audio/dsd';
$info['audio']['dataformat'] = 'dsdiff';
$info['audio']['bitrate_mode'] = 'cbr';
$info['audio']['bits_per_sample'] = 1;
$info['dsdiff'] = array();
$thisChunk = null;
while (!$this->feof() && ($ChunkHeader = $this->fread(12))) {
if (strlen($ChunkHeader) < 12) {
$this->error('Expecting chunk header at offset '.(isset($thisChunk['offset']) ? $thisChunk['offset'] : 'N/A').', found insufficient data in file, aborting parsing');
break;
}
$thisChunk = array();
$thisChunk['offset'] = $this->ftell() - 12;
$thisChunk['name'] = substr($ChunkHeader, 0, 4);
if (!preg_match('#^[\\x21-\\x7E]+ *$#', $thisChunk['name'])) {
$this->error('Invalid chunk name "'.$thisChunk['name'].'" ('.getid3_lib::PrintHexBytes($thisChunk['name']).') at offset '.$thisChunk['offset'].', aborting parsing');
}
$thisChunk['size'] = getid3_lib::BigEndian2Int(substr($ChunkHeader, 4, 8));
$datasize = $thisChunk['size'] + ($thisChunk['size'] % 2);
switch ($thisChunk['name']) {
case 'FRM8':
$thisChunk['form_type'] = $this->fread(4);
if ($thisChunk['form_type'] != 'DSD ') {
$this->error('Expecting "DSD " at offset '.($this->ftell() - 4).', found "'.getid3_lib::PrintHexBytes($thisChunk['form_type']).'", aborting parsing');
break 2;
}
break;
case 'PROP':
$thisChunk['prop_type'] = $this->fread(4);
if ($thisChunk['prop_type'] != 'SND ') {
$this->error('Expecting "SND " at offset '.($this->ftell() - 4).', found "'.getid3_lib::PrintHexBytes($thisChunk['prop_type']).'", aborting parsing');
break 2;
}
break;
case 'DIIN':
break;
case 'FVER':
if ($thisChunk['size'] == 4) {
$FVER = $this->fread(4);
$info['dsdiff']['format_version'] = ord($FVER[0]).'.'.ord($FVER[1]).'.'.ord($FVER[2]).'.'.ord($FVER[3]);
unset($FVER);
} else {
$this->warning('Expecting "FVER" chunk to be 4 bytes, found '.$thisChunk['size'].' bytes, skipping chunk');
$this->fseek($datasize, SEEK_CUR);
}
break;
case 'FS ':
if ($thisChunk['size'] == 4) {
$info['dsdiff']['sample_rate'] = getid3_lib::BigEndian2Int($this->fread(4));
$info['audio']['sample_rate'] = $info['dsdiff']['sample_rate'];
} else {
$this->warning('Expecting "FVER" chunk to be 4 bytes, found '.$thisChunk['size'].' bytes, skipping chunk');
$this->fseek($datasize, SEEK_CUR);
}
break;
case 'CHNL':
$thisChunk['num_channels'] = getid3_lib::BigEndian2Int($this->fread(2));
if ($thisChunk['num_channels'] == 0) {
$this->warning('channel count should be greater than zero, skipping chunk');
$this->fseek($datasize - 2, SEEK_CUR);
}
for ($i = 0; $i < $thisChunk['num_channels']; $i++) {
$thisChunk['channels'][$i] = $this->fread(4);
}
$info['audio']['channels'] = $thisChunk['num_channels'];
break;
case 'CMPR':
$thisChunk['compression_type'] = $this->fread(4);
$info['audio']['dataformat'] = trim($thisChunk['compression_type']);
$humanReadableByteLength = getid3_lib::BigEndian2Int($this->fread(1));
$thisChunk['compression_name'] = $this->fread($humanReadableByteLength);
if (($humanReadableByteLength % 2) == 0) {
$this->fseek(1, SEEK_CUR);
}
unset($humanReadableByteLength);
break;
case 'ABSS':
$ABSS = $this->fread(8);
$info['dsdiff']['absolute_start_time']['hours'] = getid3_lib::BigEndian2Int(substr($ABSS, 0, 2));
$info['dsdiff']['absolute_start_time']['minutes'] = getid3_lib::BigEndian2Int(substr($ABSS, 2, 1));
$info['dsdiff']['absolute_start_time']['seconds'] = getid3_lib::BigEndian2Int(substr($ABSS, 3, 1));
$info['dsdiff']['absolute_start_time']['samples'] = getid3_lib::BigEndian2Int(substr($ABSS, 4, 4));
unset($ABSS);
break;
case 'LSCO':
$thisChunk['loundspeaker_config_id'] = getid3_lib::BigEndian2Int($this->fread(2));
break;
case 'COMT':
$thisChunk['num_comments'] = getid3_lib::BigEndian2Int($this->fread(2));
for ($i = 0; $i < $thisChunk['num_comments']; $i++) {
$thisComment = array();
$COMT = $this->fread(14);
$thisComment['creation_year'] = getid3_lib::BigEndian2Int(substr($COMT, 0, 2));
$thisComment['creation_month'] = getid3_lib::BigEndian2Int(substr($COMT, 2, 1));
$thisComment['creation_day'] = getid3_lib::BigEndian2Int(substr($COMT, 3, 1));
$thisComment['creation_hour'] = getid3_lib::BigEndian2Int(substr($COMT, 4, 1));
$thisComment['creation_minute'] = getid3_lib::BigEndian2Int(substr($COMT, 5, 1));
$thisComment['comment_type_id'] = getid3_lib::BigEndian2Int(substr($COMT, 6, 2));
$thisComment['comment_ref_id'] = getid3_lib::BigEndian2Int(substr($COMT, 8, 2));
$thisComment['string_length'] = getid3_lib::BigEndian2Int(substr($COMT, 10, 4));
$thisComment['comment_text'] = $this->fread($thisComment['string_length']);
if ($thisComment['string_length'] % 2) {
$this->fseek(1, SEEK_CUR);
}
$thisComment['comment_type'] = $this->DSDIFFcmtType($thisComment['comment_type_id']);
$thisComment['comment_reference'] = $this->DSDIFFcmtRef($thisComment['comment_type_id'], $thisComment['comment_ref_id']);
$thisComment['creation_unix'] = mktime($thisComment['creation_hour'], $thisComment['creation_minute'], 0, $thisComment['creation_month'], $thisComment['creation_day'], $thisComment['creation_year']);
$thisChunk['comments'][$i] = $thisComment;
$commentkey = ($thisComment['comment_reference'] ?: 'comment');
$info['dsdiff']['comments'][$commentkey][] = $thisComment['comment_text'];
unset($thisComment);
}
break;
case 'MARK':
$MARK = $this->fread(22);
$thisChunk['marker_hours'] = getid3_lib::BigEndian2Int(substr($MARK, 0, 2));
$thisChunk['marker_minutes'] = getid3_lib::BigEndian2Int(substr($MARK, 2, 1));
$thisChunk['marker_seconds'] = getid3_lib::BigEndian2Int(substr($MARK, 3, 1));
$thisChunk['marker_samples'] = getid3_lib::BigEndian2Int(substr($MARK, 4, 4));
$thisChunk['marker_offset'] = getid3_lib::BigEndian2Int(substr($MARK, 8, 4));
$thisChunk['marker_type_id'] = getid3_lib::BigEndian2Int(substr($MARK, 12, 2));
$thisChunk['marker_channel'] = getid3_lib::BigEndian2Int(substr($MARK, 14, 2));
$thisChunk['marker_flagraw'] = getid3_lib::BigEndian2Int(substr($MARK, 16, 2));
$thisChunk['string_length'] = getid3_lib::BigEndian2Int(substr($MARK, 18, 4));
$thisChunk['description'] = ($thisChunk['string_length'] ? $this->fread($thisChunk['string_length']) : '');
if ($thisChunk['string_length'] % 2) {
$this->fseek(1, SEEK_CUR);
}
$thisChunk['marker_type'] = $this->DSDIFFmarkType($thisChunk['marker_type_id']);
unset($MARK);
break;
case 'DIAR':
case 'DITI':
$thisChunk['string_length'] = getid3_lib::BigEndian2Int($this->fread(4));
$thisChunk['description'] = ($thisChunk['string_length'] ? $this->fread($thisChunk['string_length']) : '');
if ($thisChunk['string_length'] % 2) {
$this->fseek(1, SEEK_CUR);
}
$commentKeys = array(
'DIAR' => 'artist',
'DITI' => 'title'
);
$commentkey = $commentKeys[$thisChunk['name']];
$info['dsdiff']['comments'][$commentkey][] = $thisChunk['description'];
break;
case 'EMID':
if ($thisChunk['size']) {
$thisChunk['identifier'] = $this->fread($thisChunk['size']);
}
break;
case 'ID3 ':
$endOfID3v2 = $this->ftell() + $datasize;
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true);
$getid3_temp = new getID3();
$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
$getid3_id3v2 = new getid3_id3v2($getid3_temp);
$getid3_id3v2->StartingOffset = $this->ftell();
if ($thisChunk['valid'] = $getid3_id3v2->Analyze()) {
$info['id3v2'] = $getid3_temp->info['id3v2'];
}
unset($getid3_temp, $getid3_id3v2);
$this->fseek($endOfID3v2);
break;
case 'DSD ':
case 'DST ':
$this->fseek($datasize, SEEK_CUR);
break;
default:
$this->warning('Unhandled chunk "'.$thisChunk['name'].'"');
$this->fseek($datasize, SEEK_CUR);
break;
}
@$info['dsdiff']['chunks'][] = $thisChunk;
}
if (empty($info['audio']['bitrate']) && !empty($info['audio']['channels']) && !empty($info['audio']['sample_rate']) && !empty($info['audio']['bits_per_sample'])) {
$info['audio']['bitrate'] = $info['audio']['bits_per_sample'] * $info['audio']['sample_rate'] * $info['audio']['channels'];
}
return true;
}
public static function DSDIFFcmtType($cmtType) {
static $DSDIFFcmtType = array(
0 => 'General (album) Comment',
1 => 'Channel Comment',
2 => 'Sound Source',
3 => 'File History',
);
return (isset($DSDIFFcmtType[$cmtType]) ? $DSDIFFcmtType[$cmtType] : 'reserved');
}
public static function DSDIFFcmtRef($cmtType, $cmtRef) {
static $DSDIFFcmtRef = array(
2 => array(
0 => 'DSD recording',
1 => 'Analogue recording',
2 => 'PCM recording',
),
3 => array(
0 => 'comment',
1 => 'encodeby',
2 => 'encoder',
3 => 'timezone',
4 => 'revision',
),
);
switch ($cmtType) {
case 0:
return '';
case 1:
return ($cmtRef ? 'channel '.$cmtRef : 'all channels');
case 2:
case 3:
return (isset($DSDIFFcmtRef[$cmtType][$cmtRef]) ? $DSDIFFcmtRef[$cmtType][$cmtRef] : 'reserved');
}
return 'unsupported $cmtType='.$cmtType;
}
public static function DSDIFFmarkType($markType) {
static $DSDIFFmarkType = array(
0 => 'TrackStart',
1 => 'TrackStop',
2 => 'ProgramStart',
3 => 'Obsolete',
4 => 'Index',
);
return (isset($DSDIFFmarkType[$markType]) ? $DSDIFFmarkType[$markType] : 'reserved');
}
}