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.misc.cue.php                                         //
12 // module for analyzing CUEsheet files                         //
13 // dependencies: NONE                                          //
14 //                                                             //
15 /////////////////////////////////////////////////////////////////
16 //                                                             //
17 // Module originally written [2009-Mar-25] by                  //
18 //      Nigel Barnes <ngbarnesØhotmail*com>                    //
19 // Minor reformatting and similar small changes to integrate   //
20 //   into getID3 by James Heinrich <info@getid3.org>           //
21 //                                                            ///
22 /////////////////////////////////////////////////////////////////
23 
24 /*
25  * CueSheet parser by Nigel Barnes.
26  *
27  * This is a PHP conversion of CueSharp 0.5 by Wyatt O'Day (wyday.com/cuesharp)
28  */
29 
30 /**
31  * A CueSheet class used to open and parse cuesheets.
32  *
33  */
34 
35 if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
36 	exit;
37 }
38 
39 class getid3_cue extends getid3_handler
40 {
41 	public $cuesheet = array();
42 
43 	/**
44 	 * @return bool
45 	 */
46 	public function Analyze() {
47 		$info = &$this->getid3->info;
48 
49 		$info['fileformat'] = 'cue';
50 		$this->readCueSheetFilename($info['filenamepath']);
51 		$info['cue'] = $this->cuesheet;
52 		return true;
53 	}
54 
55 	/**
56 	 * @param string $filename
57 	 *
58 	 * @return array
59 	 */
60 	public function readCueSheetFilename($filename)
61 	{
62 		$filedata = file_get_contents($filename);
63 		return $this->readCueSheet($filedata);
64 	}
65 
66 	/**
67 	 * Parses a cue sheet file.
68 	 *
69 	 * @param string $filedata
70 	 *
71 	 * @return array
72 	 */
73 	public function readCueSheet(&$filedata)
74 	{
75 		$cue_lines = array();
76 		foreach (explode("\n", str_replace("\r", null, $filedata)) as $line)
77 		{
78 			if ( (strlen($line) > 0) && ($line[0] != '#'))
79 			{
80 				$cue_lines[] = trim($line);
81 			}
82 		}
83 		$this->parseCueSheet($cue_lines);
84 
85 		return $this->cuesheet;
86 	}
87 
88 	/**
89 	 * Parses the cue sheet array.
90 	 *
91 	 * @param array $file - The cuesheet as an array of each line.
92 	 */
93 	public function parseCueSheet($file)
94 	{
95 		//-1 means still global, all others are track specific
96 		$track_on = -1;
97 
98 		for ($i=0; $i < count($file); $i++)
99 		{
100 			list($key) = explode(' ', strtolower($file[$i]), 2);
101 			switch ($key)
102 			{
103 				case 'catalog':
104 				case 'cdtextfile':
105 				case 'isrc':
106 				case 'performer':
107 				case 'songwriter':
108 				case 'title':
109 					$this->parseString($file[$i], $track_on);
110 					break;
111 				case 'file':
112 					$currentFile = $this->parseFile($file[$i]);
113 					break;
114 				case 'flags':
115 					$this->parseFlags($file[$i], $track_on);
116 					break;
117 				case 'index':
118 				case 'postgap':
119 				case 'pregap':
120 					$this->parseIndex($file[$i], $track_on);
121 					break;
122 				case 'rem':
123 					$this->parseComment($file[$i], $track_on);
124 					break;
125 				case 'track':
126 					$track_on++;
127 					$this->parseTrack($file[$i], $track_on);
128 					if (isset($currentFile)) // if there's a file
129 					{
130 						$this->cuesheet['tracks'][$track_on]['datafile'] = $currentFile;
131 					}
132 					break;
133 				default:
134 					//save discarded junk and place string[] with track it was found in
135 					$this->parseGarbage($file[$i], $track_on);
136 					break;
137 			}
138 		}
139 	}
140 
141 	/**
142 	 * Parses the REM command.
143 	 *
144 	 * @param string  $line - The line in the cue file that contains the TRACK command.
145 	 * @param integer $track_on - The track currently processing.
146 	 */
147 	public function parseComment($line, $track_on)
148 	{
149 		$explodedline = explode(' ', $line, 3);
150 		$comment_REM  = (isset($explodedline[0]) ? $explodedline[0] : '');
151 		$comment_type = (isset($explodedline[1]) ? $explodedline[1] : '');
152 		$comment_data = (isset($explodedline[2]) ? $explodedline[2] : '');
153 		if (($comment_REM == 'REM') && $comment_type) {
154 			$comment_type  = strtolower($comment_type);
155 			$commment_data = trim($comment_data, ' "');
156 			if ($track_on != -1) {
157 				$this->cuesheet['tracks'][$track_on]['comments'][$comment_type][] = $comment_data;
158 			} else {
159 				$this->cuesheet['comments'][$comment_type][] = $comment_data;
160 			}
161 		}
162 	}
163 
164 	/**
165 	 * Parses the FILE command.
166 	 *
167 	 * @param string $line - The line in the cue file that contains the FILE command.
168 	 *
169 	 * @return array - Array of FILENAME and TYPE of file..
170 	 */
171 	public function parseFile($line)
172 	{
173 		$line =            substr($line, strpos($line, ' ') + 1);
174 		$type = strtolower(substr($line, strrpos($line, ' ')));
175 
176 		//remove type
177 		$line = substr($line, 0, strrpos($line, ' ') - 1);
178 
179 		//if quotes around it, remove them.
180 		$line = trim($line, '"');
181 
182 		return array('filename'=>$line, 'type'=>$type);
183 	}
184 
185 	/**
186 	 * Parses the FLAG command.
187 	 *
188 	 * @param string  $line - The line in the cue file that contains the TRACK command.
189 	 * @param integer $track_on - The track currently processing.
190 	 */
191 	public function parseFlags($line, $track_on)
192 	{
193 		if ($track_on != -1)
194 		{
195 			foreach (explode(' ', strtolower($line)) as $type)
196 			{
197 				switch ($type)
198 				{
199 					case 'flags':
200 						// first entry in this line
201 						$this->cuesheet['tracks'][$track_on]['flags'] = array(
202 							'4ch'  => false,
203 							'data' => false,
204 							'dcp'  => false,
205 							'pre'  => false,
206 							'scms' => false,
207 						);
208 						break;
209 					case 'data':
210 					case 'dcp':
211 					case '4ch':
212 					case 'pre':
213 					case 'scms':
214 						$this->cuesheet['tracks'][$track_on]['flags'][$type] = true;
215 						break;
216 					default:
217 						break;
218 				}
219 			}
220 		}
221 	}
222 
223 	/**
224 	 * Collect any unidentified data.
225 	 *
226 	 * @param string  $line - The line in the cue file that contains the TRACK command.
227 	 * @param integer $track_on - The track currently processing.
228 	 */
229 	public function parseGarbage($line, $track_on)
230 	{
231 		if ( strlen($line) > 0 )
232 		{
233 			if ($track_on == -1)
234 			{
235 				$this->cuesheet['garbage'][] = $line;
236 			}
237 			else
238 			{
239 				$this->cuesheet['tracks'][$track_on]['garbage'][] = $line;
240 			}
241 		}
242 	}
243 
244 	/**
245 	 * Parses the INDEX command of a TRACK.
246 	 *
247 	 * @param string  $line - The line in the cue file that contains the TRACK command.
248 	 * @param integer $track_on - The track currently processing.
249 	 */
250 	public function parseIndex($line, $track_on)
251 	{
252 		$type = strtolower(substr($line, 0, strpos($line, ' ')));
253 		$line =            substr($line, strpos($line, ' ') + 1);
254 		$number = 0;
255 
256 		if ($type == 'index')
257 		{
258 			//read the index number
259 			$number = intval(substr($line, 0, strpos($line, ' ')));
260 			$line   =        substr($line, strpos($line, ' ') + 1);
261 		}
262 
263 		//extract the minutes, seconds, and frames
264 		$explodedline = explode(':', $line);
265 		$minutes = (isset($explodedline[0]) ? $explodedline[0] : '');
266 		$seconds = (isset($explodedline[1]) ? $explodedline[1] : '');
267 		$frames  = (isset($explodedline[2]) ? $explodedline[2] : '');
268 
269 		switch ($type) {
270 			case 'index':
271 				$this->cuesheet['tracks'][$track_on][$type][$number] = array('minutes'=>intval($minutes), 'seconds'=>intval($seconds), 'frames'=>intval($frames));
272 				break;
273 			case 'pregap':
274 			case 'postgap':
275 				$this->cuesheet['tracks'][$track_on][$type]          = array('minutes'=>intval($minutes), 'seconds'=>intval($seconds), 'frames'=>intval($frames));
276 				break;
277 		}
278 	}
279 
280 	/**
281 	 * @param string $line
282 	 * @param int    $track_on
283 	 */
284 	public function parseString($line, $track_on)
285 	{
286 		$category = strtolower(substr($line, 0, strpos($line, ' ')));
287 		$line     =            substr($line, strpos($line, ' ') + 1);
288 
289 		//get rid of the quotes
290 		$line = trim($line, '"');
291 
292 		switch ($category)
293 		{
294 			case 'catalog':
295 			case 'cdtextfile':
296 			case 'isrc':
297 			case 'performer':
298 			case 'songwriter':
299 			case 'title':
300 				if ($track_on == -1)
301 				{
302 					$this->cuesheet[$category] = $line;
303 				}
304 				else
305 				{
306 					$this->cuesheet['tracks'][$track_on][$category] = $line;
307 				}
308 				break;
309 			default:
310 				break;
311 		}
312 	}
313 
314 	/**
315 	 * Parses the TRACK command.
316 	 *
317 	 * @param string  $line - The line in the cue file that contains the TRACK command.
318 	 * @param integer $track_on - The track currently processing.
319 	 */
320 	public function parseTrack($line, $track_on)
321 	{
322 		$line = substr($line, strpos($line, ' ') + 1);
323 		$track = ltrim(substr($line, 0, strpos($line, ' ')), '0');
324 
325 		//find the data type.
326 		$datatype = strtolower(substr($line, strpos($line, ' ') + 1));
327 
328 		$this->cuesheet['tracks'][$track_on] = array('track_number'=>$track, 'datatype'=>$datatype);
329 	}
330 
331 }
332 
333