foobar2000 SDK  2015-01-14
cue_parser.cpp
Go to the documentation of this file.
1 #include "stdafx.h"
2 
3 namespace {
4  PFC_DECLARE_EXCEPTION(exception_cue,pfc::exception,"Invalid cuesheet");
5 }
6 
7 static bool is_numeric(char c) {return c>='0' && c<='9';}
8 
9 
10 static bool is_spacing(char c)
11 {
12  return c == ' ' || c == '\t';
13 }
14 
15 static bool is_linebreak(char c)
16 {
17  return c == '\n' || c == '\r';
18 }
19 
20 static void validate_file_type(const char * p_type,t_size p_type_length) {
21  if (
22  //standard types
23  stricmp_utf8_ex(p_type,p_type_length,"WAVE",pfc_infinite) != 0 &&
24  stricmp_utf8_ex(p_type,p_type_length,"MP3",pfc_infinite) != 0 &&
25  stricmp_utf8_ex(p_type,p_type_length,"AIFF",pfc_infinite) != 0 &&
26  //common user-entered types
27  stricmp_utf8_ex(p_type,p_type_length,"APE",pfc_infinite) != 0 &&
28  stricmp_utf8_ex(p_type,p_type_length,"FLAC",pfc_infinite) != 0 &&
29  stricmp_utf8_ex(p_type,p_type_length,"WV",pfc_infinite) != 0 &&
30  stricmp_utf8_ex(p_type,p_type_length,"WAVPACK",pfc_infinite) != 0
31  )
32  pfc::throw_exception_with_message< exception_cue >(PFC_string_formatter() << "expected WAVE, MP3 or AIFF, got : \"" << pfc::string_part(p_type,p_type_length) << "\"");
33 }
34 
35 namespace {
36 
37  class NOVTABLE cue_parser_callback
38  {
39  public:
40  virtual void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length) = 0;
41  virtual void on_track(unsigned p_index,const char * p_type,t_size p_type_length) = 0;
42  virtual void on_pregap(unsigned p_value) = 0;
43  virtual void on_index(unsigned p_index,unsigned p_value) = 0;
44  virtual void on_title(const char * p_title,t_size p_title_length) = 0;
45  virtual void on_performer(const char * p_performer,t_size p_performer_length) = 0;
46  virtual void on_songwriter(const char * p_songwriter,t_size p_songwriter_length) = 0;
47  virtual void on_isrc(const char * p_isrc,t_size p_isrc_length) = 0;
48  virtual void on_catalog(const char * p_catalog,t_size p_catalog_length) = 0;
49  virtual void on_comment(const char * p_comment,t_size p_comment_length) = 0;
50  virtual void on_flags(const char * p_flags,t_size p_flags_length) = 0;
51  };
52 
53  class NOVTABLE cue_parser_callback_meta : public cue_parser_callback
54  {
55  public:
56  virtual void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length) = 0;
57  virtual void on_track(unsigned p_index,const char * p_type,t_size p_type_length) = 0;
58  virtual void on_pregap(unsigned p_value) = 0;
59  virtual void on_index(unsigned p_index,unsigned p_value) = 0;
60  virtual void on_meta(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) = 0;
61  protected:
62  static bool is_known_meta(const char * p_name,t_size p_length)
63  {
64  static const char * metas[] = {"genre","date","discid","comment","replaygain_track_gain","replaygain_track_peak","replaygain_album_gain","replaygain_album_peak"};
65  for(t_size n=0;n<PFC_TABSIZE(metas);n++) {
66  if (!stricmp_utf8_ex(p_name,p_length,metas[n],pfc_infinite)) return true;
67  }
68  return false;
69  }
70 
71  void on_comment(const char * p_comment,t_size p_comment_length)
72  {
73  unsigned ptr = 0;
74  while(ptr < p_comment_length && !is_spacing(p_comment[ptr])) ptr++;
75  if (is_known_meta(p_comment, ptr))
76  {
77  unsigned name_length = ptr;
78  while(ptr < p_comment_length && is_spacing(p_comment[ptr])) ptr++;
79  if (ptr < p_comment_length)
80  {
81  if (p_comment[ptr] == '\"')
82  {
83  ptr++;
84  unsigned value_base = ptr;
85  while(ptr < p_comment_length && p_comment[ptr] != '\"') ptr++;
86  if (ptr == p_comment_length) pfc::throw_exception_with_message<exception_cue>("invalid REM syntax");
87  if (ptr > value_base) on_meta(p_comment,name_length,p_comment + value_base,ptr - value_base);
88  }
89  else
90  {
91  unsigned value_base = ptr;
92  while(ptr < p_comment_length /*&& !is_spacing(p_comment[ptr])*/) ptr++;
93  if (ptr > value_base) on_meta(p_comment,name_length,p_comment + value_base,ptr - value_base);
94  }
95  }
96  }
97  }
98  void on_title(const char * p_title,t_size p_title_length)
99  {
100  on_meta("title",pfc_infinite,p_title,p_title_length);
101  }
102  void on_songwriter(const char * p_songwriter,t_size p_songwriter_length) {
103  on_meta("songwriter",pfc_infinite,p_songwriter,p_songwriter_length);
104  }
105  void on_performer(const char * p_performer,t_size p_performer_length)
106  {
107  on_meta("artist",pfc_infinite,p_performer,p_performer_length);
108  }
109 
110  void on_isrc(const char * p_isrc,t_size p_isrc_length)
111  {
112  on_meta("isrc",pfc_infinite,p_isrc,p_isrc_length);
113  }
114  void on_catalog(const char * p_catalog,t_size p_catalog_length)
115  {
116  on_meta("catalog",pfc_infinite,p_catalog,p_catalog_length);
117  }
118  void on_flags(const char * p_flags,t_size p_flags_length) {}
119  };
120 
121 
122  class cue_parser_callback_retrievelist : public cue_parser_callback
123  {
124  public:
125  cue_parser_callback_retrievelist(cue_parser::t_cue_entry_list & p_out) : m_out(p_out), m_track(0), m_pregap(0), m_index0_set(false), m_index1_set(false)
126  {
127  }
128 
129  void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length)
130  {
131  validate_file_type(p_type,p_type_length);
132  m_file.set_string(p_file,p_file_length);
133  }
134 
135  void on_track(unsigned p_index,const char * p_type,t_size p_type_length)
136  {
137  if (stricmp_utf8_ex(p_type,p_type_length,"audio",pfc_infinite)) pfc::throw_exception_with_message<exception_cue>("only tracks of type AUDIO supported");
138  //if (p_index != m_track + 1) throw exception_cue("cuesheet tracks out of order");
139  if (m_track != 0) finalize_track();
140  if (m_file.is_empty()) pfc::throw_exception_with_message<exception_cue>("declaring a track with no file set");
141  m_trackfile = m_file;
142  m_track = p_index;
143  }
144 
145  void on_pregap(unsigned p_value) {m_pregap = (double) p_value / 75.0;}
146 
147  void on_index(unsigned p_index,unsigned p_value)
148  {
149  if (p_index < t_cuesheet_index_list::count)
150  {
151  switch(p_index)
152  {
153  case 0: m_index0_set = true; break;
154  case 1: m_index1_set = true; break;
155  }
156  m_index_list.m_positions[p_index] = (double) p_value / 75.0;
157  }
158  }
159 
160  void on_title(const char * p_title,t_size p_title_length) {}
161  void on_performer(const char * p_performer,t_size p_performer_length) {}
162  void on_songwriter(const char * p_songwriter,t_size p_songwriter_length) {}
163  void on_isrc(const char * p_isrc,t_size p_isrc_length) {}
164  void on_catalog(const char * p_catalog,t_size p_catalog_length) {}
165  void on_comment(const char * p_comment,t_size p_comment_length) {}
166  void on_flags(const char * p_flags,t_size p_flags_length) {}
167 
168  void finalize()
169  {
170  if (m_track != 0)
171  {
172  finalize_track();
173  m_track = 0;
174  }
175  }
176 
177  private:
178  void finalize_track()
179  {
180  if (!m_index1_set) pfc::throw_exception_with_message< exception_cue > ("INDEX 01 not set");
181  if (!m_index0_set) m_index_list.m_positions[0] = m_index_list.m_positions[1] - m_pregap;
182  if (!m_index_list.is_valid()) pfc::throw_exception_with_message< exception_cue > ("invalid index list");
183 
185  iter = m_out.insert_last();
186  if (m_trackfile.is_empty()) pfc::throw_exception_with_message< exception_cue > ("track has no file assigned");
187  iter->m_file = m_trackfile;
188  iter->m_track_number = m_track;
189  iter->m_indexes = m_index_list;
190 
191  m_index_list.reset();
192  m_index0_set = false;
193  m_index1_set = false;
194  m_pregap = 0;
195  }
196 
197  bool m_index0_set,m_index1_set;
198  t_cuesheet_index_list m_index_list;
199  double m_pregap;
200  unsigned m_track;
201  pfc::string8 m_file,m_trackfile;
203  };
204 
205  class cue_parser_callback_retrieveinfo : public cue_parser_callback_meta
206  {
207  public:
208  cue_parser_callback_retrieveinfo(file_info & p_out,unsigned p_wanted_track) : m_out(p_out), m_wanted_track(p_wanted_track), m_track(0), m_is_va(false), m_index0_set(false), m_index1_set(false), m_pregap(0), m_totaltracks(0) {}
209 
210  void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length) {}
211 
212  void on_track(unsigned p_index,const char * p_type,t_size p_type_length)
213  {
214  if (p_index == 0) pfc::throw_exception_with_message< exception_cue > ("invalid TRACK index");
215  if (p_index == m_wanted_track)
216  {
217  if (stricmp_utf8_ex(p_type,p_type_length,"audio",pfc_infinite)) pfc::throw_exception_with_message< exception_cue > ("only tracks of type AUDIO supported");
218  }
219  m_track = p_index;
220  m_totaltracks++;
221  }
222 
223  void on_pregap(unsigned p_value) {if (m_track == m_wanted_track) m_pregap = (double) p_value / 75.0;}
224 
225  void on_index(unsigned p_index,unsigned p_value)
226  {
227  if (m_track == m_wanted_track && p_index < t_cuesheet_index_list::count)
228  {
229  switch(p_index)
230  {
231  case 0: m_index0_set = true; break;
232  case 1: m_index1_set = true; break;
233  }
234  m_indexes.m_positions[p_index] = (double) p_value / 75.0;
235  }
236  }
237 
238 
239  void on_meta(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length)
240  {
242  if (m_track == 0) //globals
243  {
244  //convert global title to album
245  if (!stricmp_utf8_ex(p_name,p_name_length,"title",pfc_infinite))
246  {
247  p_name = "album";
248  p_name_length = 5;
249  }
250  else if (!stricmp_utf8_ex(p_name,p_name_length,"artist",pfc_infinite))
251  {
252  m_album_artist.set_string(p_value,p_value_length);
253  }
254 
255  iter = m_globals.insert_last();
256  }
257  else
258  {
259  if (!m_is_va)
260  {
261  if (!stricmp_utf8_ex(p_name,p_name_length,"artist",pfc_infinite))
262  {
263  if (!m_album_artist.is_empty())
264  {
265  if (stricmp_utf8_ex(p_value,p_value_length,m_album_artist,m_album_artist.length())) m_is_va = true;
266  }
267  }
268  }
269 
270  if (m_track == m_wanted_track) //locals
271  {
272  iter = m_locals.insert_last();
273  }
274  }
275  if (iter.is_valid())
276  {
277  iter->m_name.set_string(p_name,p_name_length);
278  iter->m_value.set_string(p_value,p_value_length);
279  }
280  }
281 
282  void finalize()
283  {
284  if (!m_index1_set) pfc::throw_exception_with_message< exception_cue > ("INDEX 01 not set");
285  if (!m_index0_set) m_indexes.m_positions[0] = m_indexes.m_positions[1] - m_pregap;
286  m_indexes.to_infos(m_out);
287 
288  replaygain_info rg;
289  rg.reset();
291 
292  if (m_is_va)
293  {
294  //clean up VA mess
295 
296  t_meta_list::const_iterator iter_global,iter_local;
297 
298  iter_global = find_first_field(m_globals,"artist");
299  iter_local = find_first_field(m_locals,"artist");
300  if (iter_global.is_valid())
301  {
302  m_out.meta_set("album artist",iter_global->m_value);
303  if (iter_local.is_valid()) m_out.meta_set("artist",iter_local->m_value);
304  else m_out.meta_set("artist",iter_global->m_value);
305  }
306  else
307  {
308  if (iter_local.is_valid()) m_out.meta_set("artist",iter_local->m_value);
309  }
310 
311 
312  wipe_field(m_globals,"artist");
313  wipe_field(m_locals,"artist");
314 
315  }
316 
317  for(iter=m_globals.first();iter.is_valid();iter++)
318  {
319  if (!rg.set_from_meta(iter->m_name,iter->m_value))
320  m_out.meta_set(iter->m_name,iter->m_value);
321  }
322  for(iter=m_locals.first();iter.is_valid();iter++)
323  {
324  if (!rg.set_from_meta(iter->m_name,iter->m_value))
325  m_out.meta_set(iter->m_name,iter->m_value);
326  }
327  m_out.meta_set("tracknumber",PFC_string_formatter() << m_wanted_track);
328  m_out.meta_set("totaltracks", PFC_string_formatter() << m_totaltracks);
329  m_out.set_replaygain(rg);
330 
331  }
332  private:
333  struct t_meta_entry {
334  pfc::string8 m_name,m_value;
335  };
336  typedef pfc::chain_list_v2_t<t_meta_entry> t_meta_list;
337 
338  static t_meta_list::const_iterator find_first_field(t_meta_list const & p_list,const char * p_field)
339  {
341  for(iter=p_list.first();iter.is_valid();++iter)
342  {
343  if (!stricmp_utf8(p_field,iter->m_name)) return iter;
344  }
345  return t_meta_list::const_iterator();//null iterator
346  }
347 
348  static void wipe_field(t_meta_list & p_list,const char * p_field)
349  {
351  for(iter=p_list.first();iter.is_valid();)
352  {
353  if (!stricmp_utf8(p_field,iter->m_name))
354  {
355  t_meta_list::iterator temp = iter;
356  ++temp;
357  p_list.remove_single(iter);
358  iter = temp;
359  }
360  else
361  {
362  ++iter;
363  }
364  }
365  }
366 
367  t_meta_list m_globals,m_locals;
368  file_info & m_out;
369  unsigned m_wanted_track, m_track,m_totaltracks;
370  pfc::string8 m_album_artist;
371  bool m_is_va;
372  t_cuesheet_index_list m_indexes;
373  bool m_index0_set,m_index1_set;
374  double m_pregap;
375  };
376 
377 };
378 
379 
380 static void g_parse_cue_line(const char * p_line,t_size p_line_length,cue_parser_callback & p_callback)
381 {
382  t_size ptr = 0;
383  while(ptr < p_line_length && !is_spacing(p_line[ptr])) ptr++;
384  if (!stricmp_utf8_ex(p_line,ptr,"file",pfc_infinite))
385  {
386  while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
387  t_size file_base,file_length, type_base,type_length;
388 
389  if (p_line[ptr] == '\"')
390  {
391  ptr++;
392  file_base = ptr;
393  while(ptr < p_line_length && p_line[ptr] != '\"') ptr++;
394  if (ptr == p_line_length) pfc::throw_exception_with_message< exception_cue > ("invalid FILE syntax");
395  file_length = ptr - file_base;
396  ptr++;
397  while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
398  }
399  else
400  {
401  file_base = ptr;
402  while(ptr < p_line_length && !is_spacing(p_line[ptr])) ptr++;
403  file_length = ptr - file_base;
404  while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
405  }
406 
407  type_base = ptr;
408  while(ptr < p_line_length && !is_spacing(p_line[ptr])) ptr++;
409  type_length = ptr - type_base;
410  while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
411 
412  if (ptr != p_line_length || file_length == 0 || type_length == 0) pfc::throw_exception_with_message< exception_cue > ("invalid FILE syntax");
413 
414  p_callback.on_file(p_line + file_base, file_length, p_line + type_base, type_length);
415  }
416  else if (!stricmp_utf8_ex(p_line,ptr,"track",pfc_infinite))
417  {
418  while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
419 
420  t_size track_base = ptr, track_length;
421  while(ptr < p_line_length && !is_spacing(p_line[ptr]))
422  {
423  if (!is_numeric(p_line[ptr])) pfc::throw_exception_with_message< exception_cue > ("invalid TRACK syntax");
424  ptr++;
425  }
426  track_length = ptr - track_base;
427  while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
428 
429  t_size type_base = ptr, type_length;
430  while(ptr < p_line_length && !is_spacing(p_line[ptr])) ptr++;
431  type_length = ptr - type_base;
432 
433  while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
434  if (ptr != p_line_length || type_length == 0) pfc::throw_exception_with_message< exception_cue > ("invalid TRACK syntax");
435  unsigned track = pfc::atoui_ex(p_line+track_base,track_length);
436  if (track < 1 || track > 99) pfc::throw_exception_with_message< exception_cue > ("invalid track number");
437 
438  p_callback.on_track(track,p_line + type_base, type_length);
439  }
440  else if (!stricmp_utf8_ex(p_line,ptr,"index",pfc_infinite))
441  {
442  while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
443 
444  t_size index_base,index_length, time_base,time_length;
445  index_base = ptr;
446  while(ptr < p_line_length && !is_spacing(p_line[ptr]))
447  {
448  if (!is_numeric(p_line[ptr])) pfc::throw_exception_with_message< exception_cue > ("invalid INDEX syntax" );
449  ptr++;
450  }
451  index_length = ptr - index_base;
452 
453  while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
454  time_base = ptr;
455  while(ptr < p_line_length && !is_spacing(p_line[ptr]))
456  {
457  if (!is_numeric(p_line[ptr]) && p_line[ptr] != ':')
458  pfc::throw_exception_with_message< exception_cue > ("invalid INDEX syntax");
459  ptr++;
460  }
461  time_length = ptr - time_base;
462 
463  while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
464 
465  if (ptr != p_line_length || index_length == 0 || time_length == 0)
466  pfc::throw_exception_with_message< exception_cue > ("invalid INDEX syntax");
467 
468  unsigned index = pfc::atoui_ex(p_line+index_base,index_length);
469  if (index > 99) pfc::throw_exception_with_message< exception_cue > ("invalid INDEX syntax");
470  unsigned time = cuesheet_parse_index_time_ticks_e(p_line + time_base,time_length);
471 
472  p_callback.on_index(index,time);
473  }
474  else if (!stricmp_utf8_ex(p_line,ptr,"pregap",pfc_infinite))
475  {
476  while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
477 
478  t_size time_base, time_length;
479  time_base = ptr;
480  while(ptr < p_line_length && !is_spacing(p_line[ptr]))
481  {
482  if (!is_numeric(p_line[ptr]) && p_line[ptr] != ':')
483  pfc::throw_exception_with_message< exception_cue > ("invalid PREGAP syntax");
484  ptr++;
485  }
486  time_length = ptr - time_base;
487 
488  while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
489 
490  if (ptr != p_line_length || time_length == 0)
491  pfc::throw_exception_with_message< exception_cue > ("invalid PREGAP syntax");
492 
493  unsigned time = cuesheet_parse_index_time_ticks_e(p_line + time_base,time_length);
494 
495  p_callback.on_pregap(time);
496  }
497  else if (!stricmp_utf8_ex(p_line,ptr,"title",pfc_infinite))
498  {
499  while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
500  if (ptr == p_line_length) pfc::throw_exception_with_message< exception_cue > ("invalid TITLE syntax");
501  if (p_line[ptr] == '\"')
502  {
503  ptr++;
504  t_size base = ptr;
505  while(ptr < p_line_length && p_line[ptr] != '\"') ptr++;
506  if (ptr == p_line_length) pfc::throw_exception_with_message< exception_cue > ("invalid TITLE syntax");
507  t_size length = ptr-base;
508  ptr++;
509  while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
510  if (ptr != p_line_length) pfc::throw_exception_with_message< exception_cue > ("invalid TITLE syntax");
511  p_callback.on_title(p_line+base,length);
512  }
513  else
514  {
515  p_callback.on_title(p_line+ptr,p_line_length-ptr);
516  }
517  }
518  else if (!stricmp_utf8_ex(p_line,ptr,"performer",pfc_infinite))
519  {
520  while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
521  if (ptr == p_line_length) pfc::throw_exception_with_message< exception_cue > ("invalid PERFORMER syntax");
522  if (p_line[ptr] == '\"')
523  {
524  ptr++;
525  t_size base = ptr;
526  while(ptr < p_line_length && p_line[ptr] != '\"') ptr++;
527  if (ptr == p_line_length) pfc::throw_exception_with_message< exception_cue > ("invalid PERFORMER syntax");
528  t_size length = ptr-base;
529  ptr++;
530  while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
531  if (ptr != p_line_length) pfc::throw_exception_with_message< exception_cue > ("invalid PERFORMER syntax");
532  p_callback.on_performer(p_line+base,length);
533  }
534  else
535  {
536  p_callback.on_performer(p_line+ptr,p_line_length-ptr);
537  }
538  }
539  else if (!stricmp_utf8_ex(p_line,ptr,"songwriter",pfc_infinite))
540  {
541  while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
542  if (ptr == p_line_length) pfc::throw_exception_with_message< exception_cue > ("invalid SONGWRITER syntax");
543  if (p_line[ptr] == '\"')
544  {
545  ptr++;
546  t_size base = ptr;
547  while(ptr < p_line_length && p_line[ptr] != '\"') ptr++;
548  if (ptr == p_line_length) pfc::throw_exception_with_message< exception_cue > ("invalid SONGWRITER syntax");
549  t_size length = ptr-base;
550  ptr++;
551  while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
552  if (ptr != p_line_length) pfc::throw_exception_with_message< exception_cue > ("invalid SONGWRITER syntax");
553  p_callback.on_songwriter(p_line+base,length);
554  }
555  else
556  {
557  p_callback.on_songwriter(p_line+ptr,p_line_length-ptr);
558  }
559  }
560  else if (!stricmp_utf8_ex(p_line,ptr,"isrc",pfc_infinite))
561  {
562  while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
563  t_size length = p_line_length - ptr;
564  if (length == 0) pfc::throw_exception_with_message< exception_cue > ("invalid ISRC syntax");
565  p_callback.on_isrc(p_line+ptr,length);
566  }
567  else if (!stricmp_utf8_ex(p_line,ptr,"catalog",pfc_infinite))
568  {
569  while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
570  t_size length = p_line_length - ptr;
571  if (length == 0) pfc::throw_exception_with_message< exception_cue > ("invalid CATALOG syntax");
572  p_callback.on_catalog(p_line+ptr,length);
573  }
574  else if (!stricmp_utf8_ex(p_line,ptr,"flags",pfc_infinite))
575  {
576  while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
577  if (ptr < p_line_length)
578  p_callback.on_flags(p_line + ptr, p_line_length - ptr);
579  }
580  else if (!stricmp_utf8_ex(p_line,ptr,"rem",pfc_infinite))
581  {
582  while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
583  if (ptr < p_line_length)
584  p_callback.on_comment(p_line + ptr, p_line_length - ptr);
585  }
586  else if (!stricmp_utf8_ex(p_line,ptr,"postgap",pfc_infinite)) {
587  pfc::throw_exception_with_message< exception_cue > ("POSTGAP is not supported");
588  } else if (!stricmp_utf8_ex(p_line,ptr,"cdtextfile",pfc_infinite)) {
589  //do nothing
590  }
591  else pfc::throw_exception_with_message< exception_cue > ("unknown cuesheet item");
592 }
593 
594 static void g_parse_cue(const char * p_cuesheet,cue_parser_callback & p_callback)
595 {
596  const char * parseptr = p_cuesheet;
597  t_size lineidx = 1;
598  while(*parseptr)
599  {
600  while(is_spacing(*parseptr)) parseptr++;
601  if (*parseptr)
602  {
603  t_size length = 0;
604  while(parseptr[length] && !is_linebreak(parseptr[length])) length++;
605  if (length > 0) {
606  try {
607  g_parse_cue_line(parseptr,length,p_callback);
608  } catch(exception_cue const & e) {//rethrow with line info
609  pfc::throw_exception_with_message< exception_cue > (PFC_string_formatter() << e.what() << " (line " << (unsigned)lineidx << ")");
610  }
611  }
612  parseptr += length;
613  while(is_linebreak(*parseptr)) {
614  if (*parseptr == '\n') lineidx++;
615  parseptr++;
616  }
617  }
618  }
619 }
620 
621 void cue_parser::parse(const char *p_cuesheet,t_cue_entry_list & p_out) {
622  try {
623  cue_parser_callback_retrievelist callback(p_out);
624  g_parse_cue(p_cuesheet,callback);
625  callback.finalize();
626  } catch(exception_cue const & e) {
627  pfc::throw_exception_with_message<exception_bad_cuesheet>(PFC_string_formatter() << "Error parsing cuesheet: " << e.what());
628  }
629 }
630 void cue_parser::parse_info(const char * p_cuesheet,file_info & p_info,unsigned p_index) {
631  try {
632  cue_parser_callback_retrieveinfo callback(p_info,p_index);
633  g_parse_cue(p_cuesheet,callback);
634  callback.finalize();
635  } catch(exception_cue const & e) {
636  pfc::throw_exception_with_message< exception_bad_cuesheet > (PFC_string_formatter() << "Error parsing cuesheet: " << e.what());
637  }
638 }
639 
640 namespace {
641 
642  class cue_parser_callback_retrievecount : public cue_parser_callback
643  {
644  public:
645  cue_parser_callback_retrievecount() : m_count(0) {}
646  unsigned get_count() const {return m_count;}
647  void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length) {}
648  void on_track(unsigned p_index,const char * p_type,t_size p_type_length) {m_count++;}
649  void on_pregap(unsigned p_value) {}
650  void on_index(unsigned p_index,unsigned p_value) {}
651  void on_title(const char * p_title,t_size p_title_length) {}
652  void on_performer(const char * p_performer,t_size p_performer_length) {}
653  void on_isrc(const char * p_isrc,t_size p_isrc_length) {}
654  void on_catalog(const char * p_catalog,t_size p_catalog_length) {}
655  void on_comment(const char * p_comment,t_size p_comment_length) {}
656  void on_flags(const char * p_flags,t_size p_flags_length) {}
657  private:
658  unsigned m_count;
659  };
660 
661  class cue_parser_callback_retrievecreatorentries : public cue_parser_callback
662  {
663  public:
664  cue_parser_callback_retrievecreatorentries(cue_creator::t_entry_list & p_out) : m_out(p_out), m_track(0), m_pregap(0), m_index0_set(false), m_index1_set(false) {}
665 
666  void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length) {
667  validate_file_type(p_type,p_type_length);
668  m_file.set_string(p_file,p_file_length);
669  }
670 
671  void on_track(unsigned p_index,const char * p_type,t_size p_type_length)
672  {
673  if (stricmp_utf8_ex(p_type,p_type_length,"audio",pfc_infinite)) pfc::throw_exception_with_message< exception_cue > ("only tracks of type AUDIO supported");
674  //if (p_index != m_track + 1) throw exception_cue("cuesheet tracks out of order",0);
675  if (m_track != 0) finalize_track();
676  if (m_file.is_empty()) pfc::throw_exception_with_message< exception_cue > ("declaring a track with no file set");
677  m_trackfile = m_file;
678  m_track = p_index;
679  }
680 
681  void on_pregap(unsigned p_value)
682  {
683  m_pregap = (double) p_value / 75.0;
684  }
685 
686  void on_index(unsigned p_index,unsigned p_value)
687  {
688  if (p_index < t_cuesheet_index_list::count)
689  {
690  switch(p_index)
691  {
692  case 0: m_index0_set = true; break;
693  case 1: m_index1_set = true; break;
694  }
695  m_indexes.m_positions[p_index] = (double) p_value / 75.0;
696  }
697  }
698  void on_title(const char * p_title,t_size p_title_length) {}
699  void on_performer(const char * p_performer,t_size p_performer_length) {}
700  void on_songwriter(const char * p_performer,t_size p_performer_length) {}
701  void on_isrc(const char * p_isrc,t_size p_isrc_length) {}
702  void on_catalog(const char * p_catalog,t_size p_catalog_length) {}
703  void on_comment(const char * p_comment,t_size p_comment_length) {}
704  void finalize()
705  {
706  if (m_track != 0)
707  {
708  finalize_track();
709  m_track = 0;
710  }
711  }
712  void on_flags(const char * p_flags,t_size p_flags_length) {
713  m_flags.set_string(p_flags,p_flags_length);
714  }
715  private:
716  void finalize_track()
717  {
718  if (m_track < 1 || m_track > 99) pfc::throw_exception_with_message< exception_cue > ("track number out of range");
719  if (!m_index1_set) pfc::throw_exception_with_message< exception_cue > ("INDEX 01 not set");
720  if (!m_index0_set) m_indexes.m_positions[0] = m_indexes.m_positions[1] - m_pregap;
721  if (!m_indexes.is_valid()) pfc::throw_exception_with_message< exception_cue > ("invalid index list");
722 
724  iter = m_out.insert_last();
725  iter->m_track_number = m_track;
726  iter->m_file = m_trackfile;
727  iter->m_index_list = m_indexes;
728  iter->m_flags = m_flags;
729  m_pregap = 0;
730  m_indexes.reset();
731  m_index0_set = m_index1_set = false;
732  m_flags.reset();
733  }
734 
735  bool m_index0_set,m_index1_set;
736  double m_pregap;
737  unsigned m_track;
739  pfc::string8 m_file,m_trackfile,m_flags;
740  t_cuesheet_index_list m_indexes;
741  };
742 }
743 
744 void cue_parser::parse_full(const char * p_cuesheet,cue_creator::t_entry_list & p_out) {
745  try {
746  {
747  cue_parser_callback_retrievecreatorentries callback(p_out);
748  g_parse_cue(p_cuesheet,callback);
749  callback.finalize();
750  }
751 
752  {
754  for(iter=p_out.first();iter.is_valid();++iter)
755  {
756  cue_parser_callback_retrieveinfo callback(iter->m_infos,iter->m_track_number);
757  g_parse_cue(p_cuesheet,callback);
758  callback.finalize();
759  }
760  }
761  } catch(exception_cue const & e) {
762  pfc::throw_exception_with_message< exception_bad_cuesheet > (PFC_string_formatter() << "Error parsing cuesheet: " << e.what());
763  }
764 }
static void g_parse_cue_line(const char *p_line, t_size p_line_length, cue_parser_callback &p_callback)
Definition: cue_parser.cpp:380
static void validate_file_type(const char *p_type, t_size p_type_length)
Definition: cue_parser.cpp:20
::pfc::const_iterator< t_item > const_iterator
Definition: chain_list_v2.h:31
int SHARED_EXPORT stricmp_utf8_ex(const char *p1, t_size len1, const char *p2, t_size len2)
bool set_from_meta(const char *p_name, const char *p_value)
Definition: file_info.h:27
::pfc::iterator< t_item > iterator
Definition: chain_list_v2.h:30
static bool is_linebreak(char c)
Definition: cue_parser.cpp:15
Differences between chain_list_v2_t<> and old chain_list_t<>: Iterators pointing to removed items as...
Definition: chain_list_v2.h:26
int SHARED_EXPORT stricmp_utf8(const char *p1, const char *p2)
bool is_valid() const
Definition: iterators.h:24
Structure containing ReplayGain scan results from some playable object, also providing various helper...
Definition: file_info.h:2
Main interface class for information about some playable object.
Definition: file_info.h:73
size_t t_size
Definition: int_types.h:48
void parse(const char *p_cuesheet, t_cue_entry_list &p_out)
Throws exception_bad_cuesheet on failure.
Definition: cue_parser.cpp:621
PFC_DECLARE_EXCEPTION(exception_bad_cuesheet, exception_io_data,"Invalid cuesheet")
unsigned atoui_ex(const char *p_string, t_size p_string_len)
unsigned cuesheet_parse_index_time_ticks_e(const char *p_string, t_size p_length)
Definition: chapterizer.cpp:48
static bool is_numeric(char c)
Definition: cue_parser.cpp:7
static bool is_spacing(char c)
Definition: cue_parser.cpp:10
std::exception exception
Definition: primitives.h:193
static void g_parse_cue(const char *p_cuesheet, cue_parser_callback &p_callback)
Definition: cue_parser.cpp:594
string_part_ref string_part(const char *ptr, t_size len)
Definition: string_base.h:31
void parse_full(const char *p_cuesheet, cue_creator::t_entry_list &p_out)
Throws exception_bad_cuesheet on failure.
Definition: cue_parser.cpp:744
void parse_info(const char *p_cuesheet, file_info &p_info, unsigned p_index)
Throws exception_bad_cuesheet on failure.
Definition: cue_parser.cpp:630