Mercurial > foo_out_sdl
comparison foosdk/sdk/foobar2000/helpers/cue_parser_embedding.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 using namespace cue_parser; | |
| 5 using namespace file_info_record_helper; | |
| 6 | |
| 7 #define CUE_EMBED_DEBUG 0 | |
| 8 | |
| 9 #if CUE_EMBED_DEBUG | |
| 10 static void DEBUG_INFO(file_info const& p_info, const char* what) { | |
| 11 FB2K_console_formatter() << "embeddedcue_metadata_manager : " << what << " >>>>"; | |
| 12 p_info.to_console(); | |
| 13 FB2K_console_formatter() << "<<<<\n"; | |
| 14 } | |
| 15 static void DEBUG_INFO(const track_record_list& content, const char* what) { | |
| 16 FB2K_console_formatter() << "embeddedcue_metadata_manager : " << what << " >>>>"; | |
| 17 for (auto iter = content.first(); iter.is_valid(); ++iter) { | |
| 18 FB2K_console_formatter() << "track " << iter->m_key << " >>>>"; | |
| 19 file_info_impl temp; iter->m_value.m_info.to_info(temp); temp.to_console(); | |
| 20 } | |
| 21 FB2K_console_formatter() << "<<<<\n"; | |
| 22 } | |
| 23 #else | |
| 24 #define DEBUG_INFO(info, what) | |
| 25 #endif | |
| 26 | |
| 27 static void build_cue_meta_name(const char * p_name,unsigned p_tracknumber,pfc::string_base & p_out) { | |
| 28 p_out.reset(); | |
| 29 p_out << "cue_track" << pfc::format_uint(p_tracknumber % 100,2) << "_" << p_name; | |
| 30 } | |
| 31 | |
| 32 static bool is_reserved_meta_entry(const char * p_name) { | |
| 33 return file_info::field_name_comparator::compare(p_name,"cuesheet") == 0; | |
| 34 } | |
| 35 | |
| 36 static bool is_global_meta_entry(const char * p_name) { | |
| 37 static const char header[] = "cue_track"; | |
| 38 return pfc::stricmp_ascii_ex(p_name,strlen(header),header,~0) != 0; | |
| 39 } | |
| 40 static bool is_allowed_field(const char * p_name) { | |
| 41 return !is_reserved_meta_entry(p_name) && is_global_meta_entry(p_name); | |
| 42 } | |
| 43 namespace { | |
| 44 | |
| 45 typedef pfc::avltree_t<pfc::string8,file_info::field_name_comparator> field_name_list; | |
| 46 | |
| 47 class __get_tag__enum_fields_enumerator { | |
| 48 public: | |
| 49 __get_tag__enum_fields_enumerator(field_name_list & p_out) : m_out(p_out) {} | |
| 50 void operator() (unsigned p_trackno,const track_record & p_record) { | |
| 51 if (p_trackno > 0) p_record.m_info.enumerate_meta(*this); | |
| 52 } | |
| 53 template<typename t_value> void operator() (const char * p_name,const t_value & p_value) { | |
| 54 m_out.add(p_name); | |
| 55 } | |
| 56 private: | |
| 57 field_name_list & m_out; | |
| 58 }; | |
| 59 | |
| 60 | |
| 61 class __get_tag__is_field_global_check { | |
| 62 private: | |
| 63 typedef file_info_record::t_meta_value t_value; | |
| 64 public: | |
| 65 __get_tag__is_field_global_check(const char * p_field) : m_field(p_field), m_value(NULL), m_state(true) {} | |
| 66 | |
| 67 void operator() (unsigned p_trackno,const track_record & p_record) { | |
| 68 if (p_trackno > 0 && m_state) { | |
| 69 const t_value * val = p_record.m_info.meta_query_ptr(m_field); | |
| 70 if (val == NULL) {m_state = false; return;} | |
| 71 if (m_value == NULL) { | |
| 72 m_value = val; | |
| 73 } else { | |
| 74 if (pfc::comparator_list<pfc::comparator_strcmp>::compare(*m_value,*val) != 0) { | |
| 75 m_state = false; return; | |
| 76 } | |
| 77 } | |
| 78 } | |
| 79 } | |
| 80 void finalize(file_info_record::t_meta_map & p_globals) { | |
| 81 if (m_state && m_value != NULL) { | |
| 82 p_globals.set(m_field,*m_value); | |
| 83 } | |
| 84 } | |
| 85 private: | |
| 86 const char * const m_field; | |
| 87 const t_value * m_value; | |
| 88 bool m_state; | |
| 89 }; | |
| 90 | |
| 91 class __get_tag__filter_globals { | |
| 92 public: | |
| 93 __get_tag__filter_globals(track_record_list const & p_tracks,file_info_record::t_meta_map & p_globals) : m_tracks(p_tracks), m_globals(p_globals) {} | |
| 94 | |
| 95 void operator() (const char * p_field) { | |
| 96 if (is_allowed_field(p_field)) { | |
| 97 __get_tag__is_field_global_check wrapper(p_field); | |
| 98 m_tracks.enumerate(wrapper); | |
| 99 wrapper.finalize(m_globals); | |
| 100 } | |
| 101 } | |
| 102 private: | |
| 103 const track_record_list & m_tracks; | |
| 104 file_info_record::t_meta_map & m_globals; | |
| 105 }; | |
| 106 | |
| 107 class __get_tag__local_field_filter { | |
| 108 public: | |
| 109 __get_tag__local_field_filter(const file_info_record::t_meta_map & p_globals,file_info_record::t_meta_map & p_output) : m_globals(p_globals), m_output(p_output), m_currenttrack(0) {} | |
| 110 void operator() (unsigned p_trackno,const track_record & p_track) { | |
| 111 if (p_trackno > 0) { | |
| 112 m_currenttrack = p_trackno; | |
| 113 p_track.m_info.enumerate_meta(*this); | |
| 114 } | |
| 115 } | |
| 116 void operator() (const char * p_name,const file_info_record::t_meta_value & p_value) { | |
| 117 PFC_ASSERT(m_currenttrack > 0); | |
| 118 if (!m_globals.have_item(p_name)) { | |
| 119 build_cue_meta_name(p_name,m_currenttrack,m_buffer); | |
| 120 m_output.set(m_buffer,p_value); | |
| 121 } | |
| 122 } | |
| 123 private: | |
| 124 unsigned m_currenttrack; | |
| 125 pfc::string8_fastalloc m_buffer; | |
| 126 const file_info_record::t_meta_map & m_globals; | |
| 127 file_info_record::t_meta_map & m_output; | |
| 128 }; | |
| 129 }; | |
| 130 | |
| 131 static bool meta_value_equals(const char* v1, const char* v2, bool asNumber) { | |
| 132 if (asNumber) { | |
| 133 // Special fix: leading zeros on track numbers | |
| 134 while( *v1 == '0' ) ++ v1; | |
| 135 while( *v2 == '0' ) ++ v2; | |
| 136 } | |
| 137 return strcmp(v1,v2) == 0; | |
| 138 } | |
| 139 | |
| 140 static void strip_redundant_track_meta(unsigned p_tracknumber,const file_info & p_cueinfo,file_info_record::t_meta_map & p_meta,const char * p_metaname, bool asNumber) { | |
| 141 const size_t metaindex = p_cueinfo.meta_find(p_metaname); | |
| 142 if (metaindex == SIZE_MAX) return; | |
| 143 pfc::string_formatter namelocal; | |
| 144 build_cue_meta_name(p_metaname,p_tracknumber,namelocal); | |
| 145 { | |
| 146 const file_info_record::t_meta_value * val = p_meta.query_ptr(namelocal); | |
| 147 if (val == NULL) return; | |
| 148 file_info_record::t_meta_value::const_iterator iter = val->first(); | |
| 149 for(t_size valwalk = 0, valcount = p_cueinfo.meta_enum_value_count(metaindex); valwalk < valcount; ++valwalk) { | |
| 150 if (iter.is_empty()) return; | |
| 151 | |
| 152 if (!meta_value_equals(*iter,p_cueinfo.meta_enum_value(metaindex,valwalk), asNumber)) return; | |
| 153 ++iter; | |
| 154 } | |
| 155 if (!iter.is_empty()) return; | |
| 156 } | |
| 157 //success | |
| 158 p_meta.remove(namelocal); | |
| 159 } | |
| 160 | |
| 161 // Named get_tag() for backwards compat - it doesn't just get tag, it builds the intended tag from current metadata | |
| 162 // Called prior to file update | |
| 163 void embeddedcue_metadata_manager::get_tag(file_info & p_info) const { | |
| 164 if (!have_cuesheet()) { | |
| 165 m_content.query_ptr(0u)->m_info.to_info(p_info); | |
| 166 p_info.meta_remove_field("cuesheet"); | |
| 167 return; | |
| 168 } | |
| 169 | |
| 170 cue_creator::t_entry_list entries; | |
| 171 m_content.enumerate([&entries] (unsigned p_trackno,const track_record & p_record) { | |
| 172 if (p_trackno > 0) { | |
| 173 cue_creator::t_entry_list::iterator iter = entries.insert_last(); | |
| 174 iter->m_trackType = "AUDIO"; | |
| 175 iter->m_file = p_record.m_file; | |
| 176 iter->m_flags = p_record.m_flags; | |
| 177 iter->m_index_list = p_record.m_index_list; | |
| 178 iter->m_track_number = p_trackno; | |
| 179 p_record.m_info.to_info(iter->m_infos); | |
| 180 } | |
| 181 } ); | |
| 182 pfc::string_formatter cuesheet; | |
| 183 cue_creator::create(cuesheet,entries); | |
| 184 entries.remove_all(); | |
| 185 //parse it back to see what info got stored in the cuesheet and what needs to be stored outside cuesheet in the tags | |
| 186 cue_parser::parse_full(cuesheet,entries); | |
| 187 file_info_record output; | |
| 188 | |
| 189 { | |
| 190 file_info_record::t_meta_map globals; | |
| 191 //1. find global infos and forward them | |
| 192 { | |
| 193 field_name_list fields; | |
| 194 { __get_tag__enum_fields_enumerator e(fields); m_content.enumerate(e);} | |
| 195 { __get_tag__filter_globals e(m_content,globals); fields.enumerate(e); } | |
| 196 } | |
| 197 | |
| 198 output.overwrite_meta(globals); | |
| 199 | |
| 200 //2. find local infos | |
| 201 {__get_tag__local_field_filter e(globals,output.m_meta); m_content.enumerate(e);} | |
| 202 } | |
| 203 | |
| 204 | |
| 205 //strip redundant titles, artists and tracknumbers that the cuesheet already contains | |
| 206 for(cue_creator::t_entry_list::const_iterator iter = entries.first(); iter.is_valid(); ++iter) { | |
| 207 strip_redundant_track_meta(iter->m_track_number,iter->m_infos,output.m_meta,"tracknumber", true); | |
| 208 strip_redundant_track_meta(iter->m_track_number,iter->m_infos,output.m_meta,"title", false); | |
| 209 strip_redundant_track_meta(iter->m_track_number,iter->m_infos,output.m_meta,"artist", false); | |
| 210 } | |
| 211 | |
| 212 | |
| 213 //add tech infos etc | |
| 214 | |
| 215 { | |
| 216 const track_record * rec = m_content.query_ptr((unsigned)0); | |
| 217 if (rec != NULL) { | |
| 218 output.set_length(rec->m_info.get_length()); | |
| 219 output.set_replaygain(rec->m_info.get_replaygain()); | |
| 220 output.overwrite_info(rec->m_info.m_info); | |
| 221 } | |
| 222 } | |
| 223 output.meta_set("cuesheet",cuesheet); | |
| 224 output.to_info(p_info); | |
| 225 | |
| 226 DEBUG_INFO( p_info, "get_tag" ); | |
| 227 } | |
| 228 | |
| 229 static bool resolve_cue_meta_name(const char * p_name,pfc::string_base & p_outname,unsigned & p_tracknumber) { | |
| 230 //"cue_trackNN_fieldname" | |
| 231 static const char header[] = "cue_track"; | |
| 232 if (pfc::stricmp_ascii_ex(p_name,strlen(header),header,~0) != 0) return false; | |
| 233 p_name += strlen(header); | |
| 234 if (!pfc::char_is_numeric(p_name[0]) || !pfc::char_is_numeric(p_name[1]) || p_name[2] != '_') return false; | |
| 235 unsigned tracknumber = pfc::atoui_ex(p_name,2); | |
| 236 if (tracknumber == 0) return false; | |
| 237 p_name += 3; | |
| 238 p_tracknumber = tracknumber; | |
| 239 p_outname = p_name; | |
| 240 return true; | |
| 241 } | |
| 242 | |
| 243 void embeddedcue_metadata_manager::set_tag(file_info const & p_info) { | |
| 244 | |
| 245 DEBUG_INFO( p_info, "set_tag" ); | |
| 246 | |
| 247 m_content.remove_all(); | |
| 248 | |
| 249 { | |
| 250 track_record & track0 = m_content.find_or_add((unsigned)0); | |
| 251 track0.m_info.from_info(p_info); | |
| 252 track0.m_info.m_info.set("cue_embedded","no"); | |
| 253 } | |
| 254 | |
| 255 | |
| 256 | |
| 257 const char * cuesheet = p_info.meta_get("cuesheet",0); | |
| 258 if (cuesheet == NULL) { | |
| 259 return; | |
| 260 } | |
| 261 | |
| 262 //processing order | |
| 263 //1. cuesheet content | |
| 264 //2. supplement with global metadata from the tag | |
| 265 //3. overwrite with local metadata from the tag | |
| 266 | |
| 267 { | |
| 268 cue_creator::t_entry_list entries; | |
| 269 try { | |
| 270 cue_parser::parse_full(cuesheet,entries); | |
| 271 } catch(exception_io_data const & e) { | |
| 272 console::complain("Attempting to embed an invalid cuesheet", e.what()); | |
| 273 return; | |
| 274 } | |
| 275 | |
| 276 { | |
| 277 const double length = p_info.get_length(); | |
| 278 for(cue_creator::t_entry_list::const_iterator iter = entries.first(); iter.is_valid(); ++iter ) { | |
| 279 if (iter->m_index_list.start() > length) { | |
| 280 console::info("Invalid cuesheet - index outside allowed range"); | |
| 281 return; | |
| 282 } | |
| 283 } | |
| 284 } | |
| 285 | |
| 286 for(cue_creator::t_entry_list::const_iterator iter = entries.first(); iter.is_valid(); ) { | |
| 287 cue_creator::t_entry_list::const_iterator next = iter; | |
| 288 ++next; | |
| 289 track_record & entry = m_content.find_or_add(iter->m_track_number); | |
| 290 entry.m_file = iter->m_file; | |
| 291 entry.m_flags = iter->m_flags; | |
| 292 entry.m_index_list = iter->m_index_list; | |
| 293 entry.m_info.from_info(iter->m_infos); | |
| 294 DEBUG_INFO(iter->m_infos, "set_tag cue track info" ); | |
| 295 entry.m_info.from_info_overwrite_info(p_info); | |
| 296 entry.m_info.m_info.set("cue_embedded","yes"); | |
| 297 double begin = entry.m_index_list.start(), end = next.is_valid() ? next->m_index_list.start() : p_info.get_length(); | |
| 298 if (end <= begin) throw exception_io_data(); | |
| 299 entry.m_info.set_length(end - begin); | |
| 300 iter = next; | |
| 301 } | |
| 302 } | |
| 303 | |
| 304 DEBUG_INFO( m_content, "set_tag part 1"); | |
| 305 | |
| 306 // == GLOBALS == | |
| 307 for(t_size metawalk = 0, metacount = p_info.meta_get_count(); metawalk < metacount; ++metawalk) { | |
| 308 const char * name = p_info.meta_enum_name(metawalk); | |
| 309 const t_size valuecount = p_info.meta_enum_value_count(metawalk); | |
| 310 if (valuecount > 0 && !is_reserved_meta_entry(name) && is_global_meta_entry(name)) { | |
| 311 m_content.enumerate( [&p_info, metawalk, name, valuecount] ( unsigned p_trackno, track_record & p_record ) { | |
| 312 if (p_trackno > 0) { | |
| 313 // Supplement, not overwrite | |
| 314 // 2021-02-12 fix: prefer whatever has more values | |
| 315 if (valuecount > p_record.m_info.meta_value_count(name)) { | |
| 316 p_record.m_info.transfer_meta_entry(name, p_info, metawalk); | |
| 317 } | |
| 318 } | |
| 319 } ); | |
| 320 } | |
| 321 } | |
| 322 | |
| 323 DEBUG_INFO( m_content, "set_tag part 2"); | |
| 324 | |
| 325 // == TRACK LOCALS == | |
| 326 { | |
| 327 pfc::string8_fastalloc namebuffer; | |
| 328 for(t_size metawalk = 0, metacount = p_info.meta_get_count(); metawalk < metacount; ++metawalk) { | |
| 329 const char * name = p_info.meta_enum_name(metawalk); | |
| 330 const t_size valuecount = p_info.meta_enum_value_count(metawalk); | |
| 331 unsigned trackno; | |
| 332 if (valuecount > 0 && !is_reserved_meta_entry(name) && resolve_cue_meta_name(name,namebuffer,trackno)) { | |
| 333 track_record * rec = m_content.query_ptr(trackno); | |
| 334 if (rec != NULL) { | |
| 335 rec->m_info.transfer_meta_entry(namebuffer,p_info,metawalk); | |
| 336 } | |
| 337 } | |
| 338 } | |
| 339 } | |
| 340 | |
| 341 DEBUG_INFO( m_content, "set_tag part 3"); | |
| 342 } | |
| 343 | |
| 344 void embeddedcue_metadata_manager::get_track_info(unsigned p_track,file_info & p_info) const { | |
| 345 const track_record * rec = m_content.query_ptr(p_track); | |
| 346 if (rec == NULL) throw exception_io_data(); | |
| 347 rec->m_info.to_info(p_info); | |
| 348 DEBUG_INFO( p_info, pfc::format("get_track_info(", p_track, ")" )); | |
| 349 } | |
| 350 | |
| 351 void embeddedcue_metadata_manager::set_track_info(unsigned p_track,file_info const & p_info) { | |
| 352 DEBUG_INFO( p_info, pfc::format("set_track_info(", p_track, ")" )); | |
| 353 track_record * rec = m_content.query_ptr(p_track); | |
| 354 if (rec == NULL) throw exception_io_data(); | |
| 355 rec->m_info.from_info_set_meta(p_info); | |
| 356 rec->m_info.set_replaygain(p_info.get_replaygain()); | |
| 357 } | |
| 358 | |
| 359 void embeddedcue_metadata_manager::query_track_offsets(unsigned p_track,double & p_begin,double & p_length) const { | |
| 360 const track_record * rec = m_content.query_ptr(p_track); | |
| 361 if (rec == NULL) throw exception_io_data(); | |
| 362 p_begin = rec->m_index_list.start(); | |
| 363 p_length = rec->m_info.get_length(); | |
| 364 } | |
| 365 | |
| 366 bool embeddedcue_metadata_manager::have_cuesheet() const { | |
| 367 return m_content.get_count() > 1; | |
| 368 } | |
| 369 | |
| 370 namespace { | |
| 371 class _remap_trackno_enumerator { | |
| 372 public: | |
| 373 _remap_trackno_enumerator(unsigned p_index) : m_countdown(p_index), m_result(0) {} | |
| 374 template<typename t_blah> void operator() (unsigned p_trackno,const t_blah&) { | |
| 375 if (p_trackno > 0 && m_result == 0) { | |
| 376 if (m_countdown == 0) { | |
| 377 m_result = p_trackno; | |
| 378 } else { | |
| 379 --m_countdown; | |
| 380 } | |
| 381 } | |
| 382 } | |
| 383 unsigned result() const {return m_result;} | |
| 384 private: | |
| 385 unsigned m_countdown; | |
| 386 unsigned m_result; | |
| 387 }; | |
| 388 }; | |
| 389 | |
| 390 unsigned embeddedcue_metadata_manager::remap_trackno(unsigned p_index) const { | |
| 391 if (have_cuesheet()) { | |
| 392 _remap_trackno_enumerator wrapper(p_index); | |
| 393 m_content.enumerate(wrapper); | |
| 394 return wrapper.result(); | |
| 395 } else { | |
| 396 return 0; | |
| 397 } | |
| 398 } | |
| 399 | |
| 400 t_size embeddedcue_metadata_manager::get_cue_track_count() const { | |
| 401 return m_content.get_count() - 1; | |
| 402 } | |
| 403 | |
| 404 pfc::string8 embeddedcue_metadata_manager::build_minimal_cuesheet() const { | |
| 405 cue_creator::t_entry_list entries; | |
| 406 m_content.enumerate([&entries](unsigned p_trackno, const track_record& p_record) { | |
| 407 if (p_trackno > 0) { | |
| 408 cue_creator::t_entry_list::iterator iter = entries.insert_last(); | |
| 409 iter->m_trackType = "AUDIO"; | |
| 410 iter->m_file = "Image.wav"; | |
| 411 iter->m_flags = p_record.m_flags; | |
| 412 iter->m_index_list = p_record.m_index_list; | |
| 413 iter->m_track_number = p_trackno; | |
| 414 } | |
| 415 }); | |
| 416 pfc::string_formatter cuesheet; | |
| 417 cue_creator::create(cuesheet, entries); | |
| 418 return cuesheet; | |
| 419 } |
