comparison foosdk/sdk/foobar2000/helpers/cue_parser.cpp @ 1:20d02a178406 default tip

*: check in everything else yay
author Paper <paper@tflc.us>
date Mon, 05 Jan 2026 02:15:46 -0500
parents
children
comparison
equal deleted inserted replaced
0:e9bb126753e7 1:20d02a178406
1 #include "StdAfx.h"
2 #include "cue_parser.h"
3
4
5 #define maximumCueTrackNumber 999
6
7 namespace {
8 PFC_DECLARE_EXCEPTION(exception_cue,pfc::exception,"Invalid cuesheet");
9 PFC_DECLARE_EXCEPTION(exception_cue_tracktype, exception_cue, "Not an audio track")
10 }
11
12 [[noreturn]] static void cue_fail(const char* msg) {
13 pfc::throw_exception_with_message< exception_cue >(msg);
14 }
15
16 static bool is_numeric(char c) {return c>='0' && c<='9';}
17
18
19 static bool is_spacing(char c)
20 {
21 return c == ' ' || c == '\t';
22 }
23
24 static bool is_linebreak(char c)
25 {
26 return c == '\n' || c == '\r';
27 }
28
29 static void validate_file_type(const char * p_type,t_size p_type_length) {
30 const char* const allowedTypes[] = {
31 "WAVE", "MP3", "AIFF", // standard typers
32 "APE", "FLAC", "WV", "WAVPACK", "MP4", // common user-entered types
33 "BINARY" // BINARY
34 };
35 for (auto walk : allowedTypes) {
36 if (pfc::stringEqualsI_ascii_ex(p_type, p_type_length, walk, SIZE_MAX)) return;
37 }
38 pfc::throw_exception_with_message< exception_cue >(PFC_string_formatter() << "expected WAVE, MP3 or AIFF, got : \"" << pfc::string_part(p_type,p_type_length) << "\"");
39 }
40
41 namespace {
42
43 class NOVTABLE cue_parser_callback
44 {
45 public:
46 virtual void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length) = 0;
47 virtual void on_track(unsigned p_index,const char * p_type,t_size p_type_length) = 0;
48 virtual void on_pregap(unsigned p_value) = 0;
49 virtual void on_index(unsigned p_index,unsigned p_value) = 0;
50 virtual void on_title(const char * p_title,t_size p_title_length) = 0;
51 virtual void on_performer(const char * p_performer,t_size p_performer_length) = 0;
52 virtual void on_songwriter(const char * p_songwriter,t_size p_songwriter_length) = 0;
53 virtual void on_isrc(const char * p_isrc,t_size p_isrc_length) = 0;
54 virtual void on_catalog(const char * p_catalog,t_size p_catalog_length) = 0;
55 virtual void on_comment(const char * p_comment,t_size p_comment_length) = 0;
56 virtual void on_flags(const char * p_flags,t_size p_flags_length) = 0;
57 };
58
59 class NOVTABLE cue_parser_callback_meta : public cue_parser_callback
60 {
61 public:
62 virtual void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length) = 0;
63 virtual void on_track(unsigned p_index,const char * p_type,t_size p_type_length) = 0;
64 virtual void on_pregap(unsigned p_value) = 0;
65 virtual void on_index(unsigned p_index,unsigned p_value) = 0;
66 virtual void on_meta(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) = 0;
67 protected:
68 static bool is_known_meta(const char * p_name,t_size p_length)
69 {
70 static const char * metas[] = {"genre","date","discid","comment","replaygain_track_gain","replaygain_track_peak","replaygain_album_gain","replaygain_album_peak", "discnumber", "totaldiscs"};
71 for (const char* m : metas) {
72 if (!stricmp_utf8_ex(p_name, p_length, m, SIZE_MAX)) return true;
73 }
74 return false;
75 }
76
77 void on_comment(const char * p_comment,t_size p_comment_length)
78 {
79 unsigned ptr = 0;
80 while(ptr < p_comment_length && !is_spacing(p_comment[ptr])) ptr++;
81 if (is_known_meta(p_comment, ptr))
82 {
83 unsigned name_length = ptr;
84 while(ptr < p_comment_length && is_spacing(p_comment[ptr])) ptr++;
85 if (ptr < p_comment_length)
86 {
87 if (p_comment[ptr] == '\"')
88 {
89 ptr++;
90 unsigned value_base = ptr;
91 while(ptr < p_comment_length && p_comment[ptr] != '\"') ptr++;
92 if (ptr == p_comment_length) pfc::throw_exception_with_message<exception_cue>("invalid REM syntax");
93 if (ptr > value_base) on_meta(p_comment,name_length,p_comment + value_base,ptr - value_base);
94 }
95 else
96 {
97 unsigned value_base = ptr;
98 while(ptr < p_comment_length /*&& !is_spacing(p_comment[ptr])*/) ptr++;
99 if (ptr > value_base) on_meta(p_comment,name_length,p_comment + value_base,ptr - value_base);
100 }
101 }
102 }
103 }
104 void on_title(const char * p_title,t_size p_title_length)
105 {
106 on_meta("title",pfc_infinite,p_title,p_title_length);
107 }
108 void on_songwriter(const char * p_songwriter,t_size p_songwriter_length) {
109 on_meta("songwriter",pfc_infinite,p_songwriter,p_songwriter_length);
110 }
111 void on_performer(const char * p_performer,t_size p_performer_length)
112 {
113 on_meta("artist",pfc_infinite,p_performer,p_performer_length);
114 }
115
116 void on_isrc(const char * p_isrc,t_size p_isrc_length)
117 {
118 on_meta("isrc",pfc_infinite,p_isrc,p_isrc_length);
119 }
120 void on_catalog(const char * p_catalog,t_size p_catalog_length)
121 {
122 on_meta("catalog",pfc_infinite,p_catalog,p_catalog_length);
123 }
124 void on_flags(const char * p_flags,t_size p_flags_length) {}
125 };
126
127
128 class cue_parser_callback_retrievelist : public cue_parser_callback
129 {
130 public:
131 cue_parser_callback_retrievelist(cue_parser::t_cue_entry_list& p_out) : m_out(p_out) {}
132
133 void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length)
134 {
135 validate_file_type(p_type,p_type_length);
136 m_file.set_string(p_file,p_file_length);
137 m_fileType.set_string(p_type, p_type_length);
138 }
139
140 void on_track(unsigned p_index,const char * p_type,t_size p_type_length)
141 {
142 finalize_track(); // finalize previous track
143
144 m_trackIsAudio = stricmp_utf8_ex(p_type,p_type_length,"audio",pfc_infinite) == 0;
145 if (m_file.is_empty()) pfc::throw_exception_with_message<exception_cue>("declaring a track with no file set");
146 m_trackfile = m_file;
147 m_trackFileType = m_fileType;
148 m_track = p_index;
149 }
150
151 void on_pregap(unsigned p_value) {m_pregap = (double) p_value / 75.0;}
152
153 void on_index(unsigned p_index,unsigned p_value)
154 {
155 if (p_index < t_cuesheet_index_list::count)
156 {
157 switch(p_index)
158 {
159 case 0: m_index0_set = true; break;
160 case 1: m_index1_set = true; break;
161 }
162 m_index_list.m_positions[p_index] = (double) p_value / 75.0;
163 }
164 }
165
166 void on_title(const char * p_title,t_size p_title_length) {}
167 void on_performer(const char * p_performer,t_size p_performer_length) {}
168 void on_songwriter(const char * p_songwriter,t_size p_songwriter_length) {}
169 void on_isrc(const char * p_isrc,t_size p_isrc_length) {}
170 void on_catalog(const char * p_catalog,t_size p_catalog_length) {}
171 void on_comment(const char * p_comment,t_size p_comment_length) {}
172 void on_flags(const char * p_flags,t_size p_flags_length) {}
173
174 void finalize()
175 {
176 finalize_track(); // finalize last track
177 sanity();
178 }
179
180 private:
181 void sanity() {
182 int trk = 0;
183 for (auto& iter : m_out) {
184 int i = iter.m_track_number;
185 if (i <= trk) cue_fail("incorrect track numbering");
186 trk = i;
187 }
188 }
189 void finalize_track()
190 {
191 if ( m_track != 0 && m_trackIsAudio ) {
192 if (!m_index1_set) cue_fail("INDEX 01 not set");
193 if (!m_index0_set) m_index_list.m_positions[0] = m_index_list.m_positions[1] - m_pregap;
194 if (!m_index_list.is_valid()) cue_fail("invalid index list");
195
196 cue_parser::t_cue_entry_list::iterator iter;
197 iter = m_out.insert_last();
198 if (m_trackfile.is_empty()) cue_fail("track has no file assigned");
199 iter->m_file = m_trackfile;
200 iter->m_fileType = m_trackFileType;
201 iter->m_track_number = m_track;
202 iter->m_indexes = m_index_list;
203 }
204
205 m_index_list.reset();
206 m_index0_set = false;
207 m_index1_set = false;
208 m_pregap = 0;
209
210 m_track = 0; m_trackIsAudio = false;
211 }
212
213 bool m_index0_set = false,m_index1_set = false;
214 t_cuesheet_index_list m_index_list;
215 double m_pregap = 0;
216 unsigned m_track = 0;
217 bool m_trackIsAudio = false;
218 pfc::string8 m_file,m_fileType,m_trackfile,m_trackFileType;
219 cue_parser::t_cue_entry_list & m_out;
220 };
221
222 class cue_parser_callback_retrieveinfo : public cue_parser_callback_meta
223 {
224 public:
225 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) {}
226
227 void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length) {}
228
229 void on_track(unsigned p_index,const char * p_type,t_size p_type_length)
230 {
231 if (p_index == 0) cue_fail("invalid TRACK index");
232 if (p_index == m_wanted_track)
233 {
234 if (stricmp_utf8_ex(p_type,p_type_length,"audio",pfc_infinite)) throw exception_cue_tracktype();
235 }
236 m_track = p_index;
237 m_totaltracks++;
238 }
239
240 void on_pregap(unsigned p_value) {if (m_track == m_wanted_track) m_pregap = (double) p_value / 75.0;}
241
242 void on_index(unsigned p_index,unsigned p_value)
243 {
244 if (m_track == m_wanted_track && p_index < t_cuesheet_index_list::count)
245 {
246 switch(p_index)
247 {
248 case 0: m_index0_set = true; break;
249 case 1: m_index1_set = true; break;
250 }
251 m_indexes.m_positions[p_index] = (double) p_value / 75.0;
252 }
253 }
254
255
256 void on_meta(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length)
257 {
258 t_meta_list::iterator iter;
259 if (m_track == 0) //globals
260 {
261 //convert global title to album
262 if (!stricmp_utf8_ex(p_name,p_name_length,"title",pfc_infinite))
263 {
264 p_name = "album";
265 p_name_length = 5;
266 }
267 else if (!stricmp_utf8_ex(p_name,p_name_length,"artist",pfc_infinite))
268 {
269 m_album_artist.set_string(p_value,p_value_length);
270 }
271
272 iter = m_globals.insert_last();
273 }
274 else
275 {
276 if (!m_is_va)
277 {
278 if (!stricmp_utf8_ex(p_name,p_name_length,"artist",pfc_infinite))
279 {
280 if (!m_album_artist.is_empty())
281 {
282 if (stricmp_utf8_ex(p_value,p_value_length,m_album_artist,m_album_artist.length())) m_is_va = true;
283 }
284 }
285 }
286
287 if (m_track == m_wanted_track) //locals
288 {
289 iter = m_locals.insert_last();
290 }
291 }
292 if (iter.is_valid())
293 {
294 iter->m_name.set_string(p_name,p_name_length);
295 iter->m_value.set_string(p_value,p_value_length);
296 }
297 }
298
299 void _meta_set(const char* key, const pfc::string8 & value) {
300 if ( value.length() > 0 ) m_out.meta_set( key, value );
301 }
302
303 void finalize()
304 {
305 if (!m_index1_set) pfc::throw_exception_with_message< exception_cue > ("INDEX 01 not set");
306 if (!m_index0_set) m_indexes.m_positions[0] = m_indexes.m_positions[1] - m_pregap;
307 m_indexes.to_infos(m_out);
308
309 replaygain_info rg;
310 rg.reset();
311 t_meta_list::const_iterator iter;
312
313 if (m_is_va)
314 {
315 //clean up VA mess
316
317 t_meta_list::const_iterator iter_global,iter_local;
318
319 iter_global = find_first_field(m_globals,"artist");
320 iter_local = find_first_field(m_locals,"artist");
321 if (iter_global.is_valid())
322 {
323 _meta_set("album artist",iter_global->m_value);
324 if (iter_local.is_valid()) {
325 _meta_set("artist",iter_local->m_value);
326 } else {
327 _meta_set("artist",iter_global->m_value);
328 }
329 }
330 else
331 {
332 if (iter_local.is_valid()) _meta_set("artist",iter_local->m_value);
333 }
334
335
336 wipe_field(m_globals,"artist");
337 wipe_field(m_locals,"artist");
338
339 }
340
341 for(iter=m_globals.first();iter.is_valid();iter++)
342 {
343 if (!rg.set_from_meta(iter->m_name,iter->m_value))
344 _meta_set(iter->m_name,iter->m_value);
345 }
346 for(iter=m_locals.first();iter.is_valid();iter++)
347 {
348 if (!rg.set_from_meta(iter->m_name,iter->m_value))
349 _meta_set(iter->m_name,iter->m_value);
350 }
351 m_out.meta_set("tracknumber",PFC_string_formatter() << m_wanted_track);
352 m_out.meta_set("totaltracks", PFC_string_formatter() << m_totaltracks);
353 m_out.set_replaygain(rg);
354
355 }
356 private:
357 struct t_meta_entry {
358 pfc::string8 m_name,m_value;
359 };
360 typedef pfc::chain_list_v2_t<t_meta_entry> t_meta_list;
361
362 static t_meta_list::const_iterator find_first_field(t_meta_list const & p_list,const char * p_field)
363 {
364 t_meta_list::const_iterator iter;
365 for(iter=p_list.first();iter.is_valid();++iter)
366 {
367 if (!stricmp_utf8(p_field,iter->m_name)) return iter;
368 }
369 return t_meta_list::const_iterator();//null iterator
370 }
371
372 static void wipe_field(t_meta_list & p_list,const char * p_field)
373 {
374 t_meta_list::iterator iter;
375 for(iter=p_list.first();iter.is_valid();)
376 {
377 if (!stricmp_utf8(p_field,iter->m_name))
378 {
379 t_meta_list::iterator temp = iter;
380 ++temp;
381 p_list.remove_single(iter);
382 iter = temp;
383 }
384 else
385 {
386 ++iter;
387 }
388 }
389 }
390
391 t_meta_list m_globals,m_locals;
392 file_info & m_out;
393 unsigned m_wanted_track, m_track,m_totaltracks;
394 pfc::string8 m_album_artist;
395 bool m_is_va;
396 t_cuesheet_index_list m_indexes;
397 bool m_index0_set,m_index1_set;
398 double m_pregap;
399 };
400
401 };
402
403 static pfc::string_part_ref cue_line_argument( const char * base, size_t length ) {
404 const char * end = base + length;
405 while(base < end && is_spacing(base[0]) ) ++base;
406 while(base < end && is_spacing(end[-1]) ) --end;
407 if ( base + 1 < end ) {
408 if ( base[0] == '\"' && end[-1] == '\"' ) {
409 ++base; --end;
410 }
411 }
412 return pfc::string_part(base, end-base);
413 }
414
415 static void g_parse_cue_line(const char * p_line,t_size p_line_length,cue_parser_callback & p_callback)
416 {
417 t_size ptr = 0;
418 while(ptr < p_line_length && !is_spacing(p_line[ptr])) ptr++;
419 if (!stricmp_utf8_ex(p_line,ptr,"file",pfc_infinite))
420 {
421 while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
422 t_size file_base,file_length, type_base,type_length;
423
424 if (p_line[ptr] == '\"')
425 {
426 ptr++;
427 file_base = ptr;
428 while(ptr < p_line_length && p_line[ptr] != '\"') ptr++;
429 if (ptr == p_line_length) pfc::throw_exception_with_message< exception_cue > ("invalid FILE syntax");
430 file_length = ptr - file_base;
431 ptr++;
432 while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
433 }
434 else
435 {
436 file_base = ptr;
437 while(ptr < p_line_length && !is_spacing(p_line[ptr])) ptr++;
438 file_length = ptr - file_base;
439 while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
440 }
441
442 type_base = ptr;
443 while(ptr < p_line_length && !is_spacing(p_line[ptr])) ptr++;
444 type_length = ptr - type_base;
445 while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
446
447 if (ptr != p_line_length || file_length == 0 || type_length == 0) pfc::throw_exception_with_message< exception_cue > ("invalid FILE syntax");
448
449 p_callback.on_file(p_line + file_base, file_length, p_line + type_base, type_length);
450 }
451 else if (!stricmp_utf8_ex(p_line,ptr,"track",pfc_infinite))
452 {
453 while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
454
455 t_size track_base = ptr, track_length;
456 while(ptr < p_line_length && !is_spacing(p_line[ptr]))
457 {
458 if (!is_numeric(p_line[ptr])) pfc::throw_exception_with_message< exception_cue > ("invalid TRACK syntax");
459 ptr++;
460 }
461 track_length = ptr - track_base;
462 while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
463
464 t_size type_base = ptr, type_length;
465 while(ptr < p_line_length && !is_spacing(p_line[ptr])) ptr++;
466 type_length = ptr - type_base;
467
468 while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
469 if (ptr != p_line_length || type_length == 0) pfc::throw_exception_with_message< exception_cue > ("invalid TRACK syntax");
470 unsigned track = pfc::atoui_ex(p_line+track_base,track_length);
471 if (track < 1 || track > maximumCueTrackNumber) pfc::throw_exception_with_message< exception_cue > ("invalid track number");
472
473 p_callback.on_track(track,p_line + type_base, type_length);
474 }
475 else if (!stricmp_utf8_ex(p_line,ptr,"index",pfc_infinite))
476 {
477 while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
478
479 t_size index_base,index_length, time_base,time_length;
480 index_base = ptr;
481 while(ptr < p_line_length && !is_spacing(p_line[ptr]))
482 {
483 if (!is_numeric(p_line[ptr])) pfc::throw_exception_with_message< exception_cue > ("invalid INDEX syntax" );
484 ptr++;
485 }
486 index_length = ptr - index_base;
487
488 while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
489 time_base = ptr;
490 while(ptr < p_line_length && !is_spacing(p_line[ptr]))
491 {
492 if (!is_numeric(p_line[ptr]) && p_line[ptr] != ':')
493 pfc::throw_exception_with_message< exception_cue > ("invalid INDEX syntax");
494 ptr++;
495 }
496 time_length = ptr - time_base;
497
498 while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
499
500 if (ptr != p_line_length || index_length == 0 || time_length == 0)
501 pfc::throw_exception_with_message< exception_cue > ("invalid INDEX syntax");
502
503 unsigned index = pfc::atoui_ex(p_line+index_base,index_length);
504 if (index > maximumCueTrackNumber) pfc::throw_exception_with_message< exception_cue > ("invalid INDEX syntax");
505 unsigned time = cuesheet_parse_index_time_ticks_e(p_line + time_base,time_length);
506
507 p_callback.on_index(index,time);
508 }
509 else if (!stricmp_utf8_ex(p_line,ptr,"pregap",pfc_infinite))
510 {
511 while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
512
513 t_size time_base, time_length;
514 time_base = ptr;
515 while(ptr < p_line_length && !is_spacing(p_line[ptr]))
516 {
517 if (!is_numeric(p_line[ptr]) && p_line[ptr] != ':')
518 pfc::throw_exception_with_message< exception_cue > ("invalid PREGAP syntax");
519 ptr++;
520 }
521 time_length = ptr - time_base;
522
523 while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
524
525 if (ptr != p_line_length || time_length == 0)
526 pfc::throw_exception_with_message< exception_cue > ("invalid PREGAP syntax");
527
528 unsigned time = cuesheet_parse_index_time_ticks_e(p_line + time_base,time_length);
529
530 p_callback.on_pregap(time);
531 }
532 else if (!stricmp_utf8_ex(p_line,ptr,"title",pfc_infinite))
533 {
534 auto arg = cue_line_argument(p_line+ptr, p_line_length-ptr);
535 if ( arg.m_len > 0 ) p_callback.on_title( arg.m_ptr, arg.m_len );
536 }
537 else if (!stricmp_utf8_ex(p_line,ptr,"performer",pfc_infinite))
538 {
539 auto arg = cue_line_argument(p_line + ptr, p_line_length - ptr);
540 // 2021-01 fix: allow blank performer
541 /*if (arg.m_len > 0)*/ p_callback.on_performer(arg.m_ptr, arg.m_len);
542 }
543 else if (!stricmp_utf8_ex(p_line,ptr,"songwriter",pfc_infinite))
544 {
545 auto arg = cue_line_argument(p_line + ptr, p_line_length - ptr);
546 if (arg.m_len > 0) p_callback.on_songwriter(arg.m_ptr, arg.m_len);
547 }
548 else if (!stricmp_utf8_ex(p_line,ptr,"isrc",pfc_infinite))
549 {
550 while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
551 t_size length = p_line_length - ptr;
552 if (length == 0) pfc::throw_exception_with_message< exception_cue > ("invalid ISRC syntax");
553 p_callback.on_isrc(p_line+ptr,length);
554 }
555 else if (!stricmp_utf8_ex(p_line,ptr,"catalog",pfc_infinite))
556 {
557 while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
558 t_size length = p_line_length - ptr;
559 if (length == 0) pfc::throw_exception_with_message< exception_cue > ("invalid CATALOG syntax");
560 p_callback.on_catalog(p_line+ptr,length);
561 }
562 else if (!stricmp_utf8_ex(p_line,ptr,"flags",pfc_infinite))
563 {
564 while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
565 if (ptr < p_line_length)
566 p_callback.on_flags(p_line + ptr, p_line_length - ptr);
567 }
568 else if (!stricmp_utf8_ex(p_line,ptr,"rem",pfc_infinite))
569 {
570 while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
571 if (ptr < p_line_length)
572 p_callback.on_comment(p_line + ptr, p_line_length - ptr);
573 }
574 else if (!stricmp_utf8_ex(p_line,ptr,"postgap",pfc_infinite)) {
575 pfc::throw_exception_with_message< exception_cue > ("POSTGAP is not supported");
576 } else if (!stricmp_utf8_ex(p_line,ptr,"cdtextfile",pfc_infinite)) {
577 //do nothing
578 }
579 else pfc::throw_exception_with_message< exception_cue > ("unknown cuesheet item");
580 }
581
582 static void g_parse_cue(const char * p_cuesheet,cue_parser_callback & p_callback)
583 {
584 const char * parseptr = p_cuesheet;
585 t_size lineidx = 1;
586 while(*parseptr)
587 {
588 while(is_spacing(*parseptr)) parseptr++;
589 if (*parseptr)
590 {
591 t_size length = 0;
592 while(parseptr[length] && !is_linebreak(parseptr[length])) length++;
593 if (length > 0) {
594 try {
595 g_parse_cue_line(parseptr,length,p_callback);
596 } catch(exception_cue const & e) {//rethrow with line info
597 pfc::throw_exception_with_message< exception_cue > (PFC_string_formatter() << e.what() << " (line " << (unsigned)lineidx << ")");
598 }
599 }
600 parseptr += length;
601 while(is_linebreak(*parseptr)) {
602 if (*parseptr == '\n') lineidx++;
603 parseptr++;
604 }
605 }
606 }
607 }
608
609 void cue_parser::parse(const char *p_cuesheet,t_cue_entry_list & p_out) {
610 try {
611 cue_parser_callback_retrievelist callback(p_out);
612 g_parse_cue(p_cuesheet,callback);
613 callback.finalize();
614 } catch(exception_cue const & e) {
615 pfc::throw_exception_with_message<exception_bad_cuesheet>(PFC_string_formatter() << "Error parsing cuesheet: " << e.what());
616 }
617 }
618 void cue_parser::parse_info(const char * p_cuesheet,file_info & p_info,unsigned p_index) {
619 try {
620 cue_parser_callback_retrieveinfo callback(p_info,p_index);
621 g_parse_cue(p_cuesheet,callback);
622 callback.finalize();
623 } catch(exception_cue const & e) {
624 pfc::throw_exception_with_message< exception_bad_cuesheet > (PFC_string_formatter() << "Error parsing cuesheet: " << e.what());
625 }
626 }
627
628 namespace {
629
630 class cue_parser_callback_retrievecount : public cue_parser_callback
631 {
632 public:
633 cue_parser_callback_retrievecount() : m_count(0) {}
634 unsigned get_count() const {return m_count;}
635 void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length) {}
636 void on_track(unsigned p_index,const char * p_type,t_size p_type_length) {m_count++;}
637 void on_pregap(unsigned p_value) {}
638 void on_index(unsigned p_index,unsigned p_value) {}
639 void on_title(const char * p_title,t_size p_title_length) {}
640 void on_performer(const char * p_performer,t_size p_performer_length) {}
641 void on_isrc(const char * p_isrc,t_size p_isrc_length) {}
642 void on_catalog(const char * p_catalog,t_size p_catalog_length) {}
643 void on_comment(const char * p_comment,t_size p_comment_length) {}
644 void on_flags(const char * p_flags,t_size p_flags_length) {}
645 private:
646 unsigned m_count;
647 };
648
649 class cue_parser_callback_retrievecreatorentries : public cue_parser_callback
650 {
651 public:
652 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) {}
653
654 void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length) {
655 validate_file_type(p_type,p_type_length);
656 m_file.set_string(p_file,p_file_length);
657 m_fileType.set_string(p_type, p_type_length);
658 }
659
660 void on_track(unsigned p_index,const char * p_type,t_size p_type_length)
661 {
662 finalize_track();
663
664 m_trackType.set_string( p_type, p_type_length );
665
666 //if (p_index != m_track + 1) throw exception_cue("cuesheet tracks out of order",0);
667
668 if (m_file.is_empty()) pfc::throw_exception_with_message< exception_cue > ("declaring a track with no file set");
669 m_trackfile = m_file;
670 m_trackFileType = m_fileType;
671 m_track = p_index;
672 }
673
674 void on_pregap(unsigned p_value)
675 {
676 m_pregap = (double) p_value / 75.0;
677 }
678
679 void on_index(unsigned p_index,unsigned p_value)
680 {
681 if (p_index < t_cuesheet_index_list::count)
682 {
683 switch(p_index)
684 {
685 case 0: m_index0_set = true; break;
686 case 1: m_index1_set = true; break;
687 }
688 m_indexes.m_positions[p_index] = (double) p_value / 75.0;
689 }
690 }
691 void on_title(const char * p_title,t_size p_title_length) {}
692 void on_performer(const char * p_performer,t_size p_performer_length) {}
693 void on_songwriter(const char * p_performer,t_size p_performer_length) {}
694 void on_isrc(const char * p_isrc,t_size p_isrc_length) {}
695 void on_catalog(const char * p_catalog,t_size p_catalog_length) {}
696 void on_comment(const char * p_comment,t_size p_comment_length) {}
697 void finalize()
698 {
699 finalize_track();
700 }
701 void on_flags(const char * p_flags,t_size p_flags_length) {
702 m_flags.set_string(p_flags,p_flags_length);
703 }
704 private:
705 void finalize_track()
706 {
707 if ( m_track != 0 ) {
708 if (m_track < 1 || m_track > maximumCueTrackNumber) pfc::throw_exception_with_message< exception_cue > ("track number out of range");
709 if (!m_index1_set) pfc::throw_exception_with_message< exception_cue > ("INDEX 01 not set");
710 if (!m_index0_set) m_indexes.m_positions[0] = m_indexes.m_positions[1] - m_pregap;
711 if (!m_indexes.is_valid()) pfc::throw_exception_with_message< exception_cue > ("invalid index list");
712
713 cue_creator::t_entry_list::iterator iter;
714 iter = m_out.insert_last();
715 iter->m_track_number = m_track;
716 iter->m_file = m_trackfile;
717 iter->m_fileType = m_trackFileType;
718 iter->m_index_list = m_indexes;
719 iter->m_flags = m_flags;
720 iter->m_trackType = m_trackType;
721 }
722 m_pregap = 0;
723 m_indexes.reset();
724 m_index0_set = m_index1_set = false;
725 m_flags.reset();
726 m_trackType.reset();
727 }
728
729 bool m_index0_set,m_index1_set;
730 double m_pregap;
731 unsigned m_track;
732 cue_creator::t_entry_list & m_out;
733 pfc::string8 m_file, m_fileType,m_trackfile, m_trackFileType, m_flags, m_trackType;
734 t_cuesheet_index_list m_indexes;
735 };
736 }
737
738 void cue_parser::parse_full(const char * p_cuesheet,cue_creator::t_entry_list & p_out) {
739 try {
740 {
741 cue_parser_callback_retrievecreatorentries callback(p_out);
742 g_parse_cue(p_cuesheet,callback);
743 callback.finalize();
744 }
745
746 {
747 cue_creator::t_entry_list::iterator iter;
748 for(iter=p_out.first();iter.is_valid();++iter) {
749 if ( iter->isTrackAudio() ) {
750 cue_parser_callback_retrieveinfo callback(iter->m_infos,iter->m_track_number);
751 g_parse_cue(p_cuesheet,callback);
752 callback.finalize();
753 }
754 }
755 }
756 } catch(exception_cue const & e) {
757 pfc::throw_exception_with_message< exception_bad_cuesheet > (PFC_string_formatter() << "Error parsing cuesheet: " << e.what());
758 }
759 }
760
761 namespace file_info_record_helper {
762 namespace {
763 class __file_info_record__info__enumerator {
764 public:
765 __file_info_record__info__enumerator(file_info & p_out) : m_out(p_out) {}
766 void operator() (const char * p_name, const char * p_value) { m_out.__info_add_unsafe(p_name, p_value); }
767 private:
768 file_info & m_out;
769 };
770
771 class __file_info_record__meta__enumerator {
772 public:
773 __file_info_record__meta__enumerator(file_info & p_out) : m_out(p_out) {}
774 template<typename t_value> void operator() (const char * p_name, const t_value & p_value) {
775 t_size index = ~0;
776 for (typename t_value::const_iterator iter = p_value.first(); iter.is_valid(); ++iter) {
777 if (index == ~0) index = m_out.__meta_add_unsafe(p_name, *iter);
778 else m_out.meta_add_value(index, *iter);
779 }
780 }
781 private:
782 file_info & m_out;
783 };
784 }
785
786 void file_info_record::from_info(const file_info & p_info) {
787 reset();
788 m_length = p_info.get_length();
789 m_replaygain = p_info.get_replaygain();
790 from_info_overwrite_meta(p_info);
791 from_info_overwrite_info(p_info);
792 }
793 void file_info_record::to_info(file_info & p_info) const {
794 p_info.reset();
795 p_info.set_length(m_length);
796 p_info.set_replaygain(m_replaygain);
797
798 {
799 __file_info_record__info__enumerator e(p_info);
800 m_info.enumerate(e);
801 }
802 {
803 __file_info_record__meta__enumerator e(p_info);
804 m_meta.enumerate(e);
805 }
806 }
807
808 void file_info_record::reset() {
809 m_meta.remove_all(); m_info.remove_all();
810 m_length = 0;
811 m_replaygain = replaygain_info_invalid;
812 }
813
814 void file_info_record::from_info_overwrite_info(const file_info & p_info) {
815 for (t_size infowalk = 0, infocount = p_info.info_get_count(); infowalk < infocount; ++infowalk) {
816 m_info.set(p_info.info_enum_name(infowalk), p_info.info_enum_value(infowalk));
817 }
818 }
819 void file_info_record::from_info_overwrite_meta(const file_info & p_info) {
820 for (t_size metawalk = 0, metacount = p_info.meta_get_count(); metawalk < metacount; ++metawalk) {
821 const t_size valuecount = p_info.meta_enum_value_count(metawalk);
822 if (valuecount > 0) {
823 t_meta_value & entry = m_meta.find_or_add(p_info.meta_enum_name(metawalk));
824 entry.remove_all();
825 for (t_size valuewalk = 0; valuewalk < valuecount; ++valuewalk) {
826 entry.add_item(p_info.meta_enum_value(metawalk, valuewalk));
827 }
828 }
829 }
830 }
831
832 void file_info_record::from_info_overwrite_rg(const file_info & p_info) {
833 m_replaygain = replaygain_info::g_merge(m_replaygain, p_info.get_replaygain());
834 }
835
836 void file_info_record::merge_overwrite(const file_info & p_info) {
837 from_info_overwrite_info(p_info);
838 from_info_overwrite_meta(p_info);
839 from_info_overwrite_rg(p_info);
840 }
841
842 void file_info_record::transfer_meta_entry(const char * p_name, const file_info & p_info, t_size p_index) {
843 const t_size count = p_info.meta_enum_value_count(p_index);
844 if (count == 0) {
845 m_meta.remove(p_name);
846 } else {
847 t_meta_value & val = m_meta.find_or_add(p_name);
848 val.remove_all();
849 for (t_size walk = 0; walk < count; ++walk) {
850 val.add_item(p_info.meta_enum_value(p_index, walk));
851 }
852 }
853 }
854
855 void file_info_record::meta_set(const char * p_name, const char * p_value) {
856 m_meta.find_or_add(p_name).set_single(p_value);
857 }
858
859 const file_info_record::t_meta_value * file_info_record::meta_query_ptr(const char * p_name) const {
860 return m_meta.query_ptr(p_name);
861 }
862 size_t file_info_record::meta_value_count(const char* name) const {
863 auto v = meta_query_ptr(name);
864 if (v == nullptr) return 0;
865 return v->get_count();
866 }
867
868
869 void file_info_record::from_info_set_meta(const file_info & p_info) {
870 m_meta.remove_all();
871 from_info_overwrite_meta(p_info);
872 }
873
874 }