Mercurial > foo_out_sdl
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 } |
