1<?php
2
3/////////////////////////////////////////////////////////////////
4/// getID3() by James Heinrich <info@getid3.org>               //
5//  available at https://github.com/JamesHeinrich/getID3       //
6//            or https://www.getid3.org                        //
7//            or http://getid3.sourceforge.net                 //
8//  see readme.txt for more details                            //
9/////////////////////////////////////////////////////////////////
10//                                                             //
11// module.audio.dsf.php                                        //
12// module for analyzing dsf/DSF Audio files                    //
13// dependencies: module.tag.id3v2.php                          //
14//                                                            ///
15/////////////////////////////////////////////////////////////////
16
17if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
18	exit;
19}
20getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true);
21
22class getid3_dsf extends getid3_handler
23{
24	/**
25	 * @return bool
26	 */
27	public function Analyze() {
28		$info = &$this->getid3->info;
29
30		$info['fileformat']            = 'dsf';
31		$info['audio']['dataformat']   = 'dsf';
32		$info['audio']['lossless']     = true;
33		$info['audio']['bitrate_mode'] = 'cbr';
34
35		$this->fseek($info['avdataoffset']);
36		$dsfheader = $this->fread(28 + 12);
37
38		$headeroffset = 0;
39		$info['dsf']['dsd']['magic'] = substr($dsfheader, $headeroffset, 4);
40		$headeroffset += 4;
41		$magic = 'DSD ';
42		if ($info['dsf']['dsd']['magic'] != $magic) {
43			$this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['dsf']['dsd']['magic']).'"');
44			unset($info['fileformat']);
45			unset($info['audio']);
46			unset($info['dsf']);
47			return false;
48		}
49		$info['dsf']['dsd']['dsd_chunk_size']     = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 8)); // should be 28
50		$headeroffset += 8;
51		$info['dsf']['dsd']['dsf_file_size']      = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 8));
52		$headeroffset += 8;
53		$info['dsf']['dsd']['meta_chunk_offset']  = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 8));
54		$headeroffset += 8;
55
56
57		$info['dsf']['fmt']['magic'] = substr($dsfheader, $headeroffset, 4);
58		$headeroffset += 4;
59		$magic = 'fmt ';
60		if ($info['dsf']['fmt']['magic'] != $magic) {
61			$this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$headeroffset.', found "'.getid3_lib::PrintHexBytes($info['dsf']['fmt']['magic']).'"');
62			return false;
63		}
64		$info['dsf']['fmt']['fmt_chunk_size']     = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 8));  // usually 52 bytes
65		$headeroffset += 8;
66		$dsfheader .= $this->fread($info['dsf']['fmt']['fmt_chunk_size'] - 12 + 12);  // we have already read the entire DSD chunk, plus 12 bytes of FMT. We now want to read the size of FMT, plus 12 bytes into the next chunk to get magic and size.
67		if (strlen($dsfheader) != ($info['dsf']['dsd']['dsd_chunk_size'] + $info['dsf']['fmt']['fmt_chunk_size'] + 12)) {
68			$this->error('Expecting '.($info['dsf']['dsd']['dsd_chunk_size'] + $info['dsf']['fmt']['fmt_chunk_size']).' bytes header, found '.strlen($dsfheader).' bytes');
69			return false;
70		}
71		$info['dsf']['fmt']['format_version']     = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 4));  // usually "1"
72		$headeroffset += 4;
73		$info['dsf']['fmt']['format_id']          = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 4));  // usually "0" = "DSD Raw"
74		$headeroffset += 4;
75		$info['dsf']['fmt']['channel_type_id']    = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 4));
76		$headeroffset += 4;
77		$info['dsf']['fmt']['channels']           = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 4));
78		$headeroffset += 4;
79		$info['dsf']['fmt']['sample_rate']        = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 4));
80		$headeroffset += 4;
81		$info['dsf']['fmt']['bits_per_sample']    = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 4));
82		$headeroffset += 4;
83		$info['dsf']['fmt']['sample_count']       = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 8));
84		$headeroffset += 8;
85		$info['dsf']['fmt']['channel_block_size'] = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 4));
86		$headeroffset += 4;
87		$info['dsf']['fmt']['reserved']           = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 4)); // zero-filled
88		$headeroffset += 4;
89
90
91		$info['dsf']['data']['magic'] = substr($dsfheader, $headeroffset, 4);
92		$headeroffset += 4;
93		$magic = 'data';
94		if ($info['dsf']['data']['magic'] != $magic) {
95			$this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$headeroffset.', found "'.getid3_lib::PrintHexBytes($info['dsf']['data']['magic']).'"');
96			return false;
97		}
98		$info['dsf']['data']['data_chunk_size']    = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 8));
99		$headeroffset += 8;
100		$info['avdataoffset'] = $headeroffset;
101		$info['avdataend']    = $info['avdataoffset'] + $info['dsf']['data']['data_chunk_size'];
102
103
104		if ($info['dsf']['dsd']['meta_chunk_offset'] > 0) {
105			$getid3_id3v2 = new getid3_id3v2($this->getid3);
106			$getid3_id3v2->StartingOffset = $info['dsf']['dsd']['meta_chunk_offset'];
107			$getid3_id3v2->Analyze();
108			unset($getid3_id3v2);
109		}
110
111
112		$info['dsf']['fmt']['channel_type'] = $this->DSFchannelTypeLookup($info['dsf']['fmt']['channel_type_id']);
113		$info['audio']['channelmode']       = $info['dsf']['fmt']['channel_type'];
114		$info['audio']['bits_per_sample']   = $info['dsf']['fmt']['bits_per_sample'];
115		$info['audio']['sample_rate']       = $info['dsf']['fmt']['sample_rate'];
116		$info['audio']['channels']          = $info['dsf']['fmt']['channels'];
117		$info['audio']['bitrate']           = $info['audio']['bits_per_sample'] * $info['audio']['sample_rate'] * $info['audio']['channels'];
118		$info['playtime_seconds']           = ($info['dsf']['data']['data_chunk_size'] * 8) / $info['audio']['bitrate'];
119
120		return true;
121	}
122
123	/**
124	 * @param int $channel_type_id
125	 *
126	 * @return string
127	 */
128	public static function DSFchannelTypeLookup($channel_type_id) {
129		static $DSFchannelTypeLookup = array(
130			                  // interleaving order:
131			1 => 'mono',      // 1: Mono
132			2 => 'stereo',    // 1: Front-Left; 2: Front-Right
133			3 => '3-channel', // 1: Front-Left; 2: Front-Right; 3: Center
134			4 => 'quad',      // 1: Front-Left; 2: Front-Right; 3: Back-Left; 4: Back-Right
135			5 => '4-channel', // 1: Front-Left; 2: Front-Right; 3: Center;    4: Low-Frequency
136			6 => '5-channel', // 1: Front-Left; 2: Front-Right; 3: Center;    4: Back-Left      5: Back-Right
137			7 => '5.1',       // 1: Front-Left; 2: Front-Right; 3: Center;    4: Low-Frequency; 5: Back-Left;  6: Back-Right
138		);
139		return (isset($DSFchannelTypeLookup[$channel_type_id]) ? $DSFchannelTypeLookup[$channel_type_id] : '');
140	}
141
142}
143