|
1
|
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 } |