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
35if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
36	exit;
37}
38
39class 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