Mercurial > foo_out_sdl
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/foosdk/sdk/foobar2000/helpers/cue_parser_embedding.cpp Mon Jan 05 02:15:46 2026 -0500 @@ -0,0 +1,419 @@ +#include "StdAfx.h" +#include "cue_parser.h" + +using namespace cue_parser; +using namespace file_info_record_helper; + +#define CUE_EMBED_DEBUG 0 + +#if CUE_EMBED_DEBUG +static void DEBUG_INFO(file_info const& p_info, const char* what) { + FB2K_console_formatter() << "embeddedcue_metadata_manager : " << what << " >>>>"; + p_info.to_console(); + FB2K_console_formatter() << "<<<<\n"; +} +static void DEBUG_INFO(const track_record_list& content, const char* what) { + FB2K_console_formatter() << "embeddedcue_metadata_manager : " << what << " >>>>"; + for (auto iter = content.first(); iter.is_valid(); ++iter) { + FB2K_console_formatter() << "track " << iter->m_key << " >>>>"; + file_info_impl temp; iter->m_value.m_info.to_info(temp); temp.to_console(); + } + FB2K_console_formatter() << "<<<<\n"; +} +#else +#define DEBUG_INFO(info, what) +#endif + +static void build_cue_meta_name(const char * p_name,unsigned p_tracknumber,pfc::string_base & p_out) { + p_out.reset(); + p_out << "cue_track" << pfc::format_uint(p_tracknumber % 100,2) << "_" << p_name; +} + +static bool is_reserved_meta_entry(const char * p_name) { + return file_info::field_name_comparator::compare(p_name,"cuesheet") == 0; +} + +static bool is_global_meta_entry(const char * p_name) { + static const char header[] = "cue_track"; + return pfc::stricmp_ascii_ex(p_name,strlen(header),header,~0) != 0; +} +static bool is_allowed_field(const char * p_name) { + return !is_reserved_meta_entry(p_name) && is_global_meta_entry(p_name); +} +namespace { + + typedef pfc::avltree_t<pfc::string8,file_info::field_name_comparator> field_name_list; + + class __get_tag__enum_fields_enumerator { + public: + __get_tag__enum_fields_enumerator(field_name_list & p_out) : m_out(p_out) {} + void operator() (unsigned p_trackno,const track_record & p_record) { + if (p_trackno > 0) p_record.m_info.enumerate_meta(*this); + } + template<typename t_value> void operator() (const char * p_name,const t_value & p_value) { + m_out.add(p_name); + } + private: + field_name_list & m_out; + }; + + + class __get_tag__is_field_global_check { + private: + typedef file_info_record::t_meta_value t_value; + public: + __get_tag__is_field_global_check(const char * p_field) : m_field(p_field), m_value(NULL), m_state(true) {} + + void operator() (unsigned p_trackno,const track_record & p_record) { + if (p_trackno > 0 && m_state) { + const t_value * val = p_record.m_info.meta_query_ptr(m_field); + if (val == NULL) {m_state = false; return;} + if (m_value == NULL) { + m_value = val; + } else { + if (pfc::comparator_list<pfc::comparator_strcmp>::compare(*m_value,*val) != 0) { + m_state = false; return; + } + } + } + } + void finalize(file_info_record::t_meta_map & p_globals) { + if (m_state && m_value != NULL) { + p_globals.set(m_field,*m_value); + } + } + private: + const char * const m_field; + const t_value * m_value; + bool m_state; + }; + + class __get_tag__filter_globals { + public: + __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) {} + + void operator() (const char * p_field) { + if (is_allowed_field(p_field)) { + __get_tag__is_field_global_check wrapper(p_field); + m_tracks.enumerate(wrapper); + wrapper.finalize(m_globals); + } + } + private: + const track_record_list & m_tracks; + file_info_record::t_meta_map & m_globals; + }; + + class __get_tag__local_field_filter { + public: + __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) {} + void operator() (unsigned p_trackno,const track_record & p_track) { + if (p_trackno > 0) { + m_currenttrack = p_trackno; + p_track.m_info.enumerate_meta(*this); + } + } + void operator() (const char * p_name,const file_info_record::t_meta_value & p_value) { + PFC_ASSERT(m_currenttrack > 0); + if (!m_globals.have_item(p_name)) { + build_cue_meta_name(p_name,m_currenttrack,m_buffer); + m_output.set(m_buffer,p_value); + } + } + private: + unsigned m_currenttrack; + pfc::string8_fastalloc m_buffer; + const file_info_record::t_meta_map & m_globals; + file_info_record::t_meta_map & m_output; + }; +}; + +static bool meta_value_equals(const char* v1, const char* v2, bool asNumber) { + if (asNumber) { + // Special fix: leading zeros on track numbers + while( *v1 == '0' ) ++ v1; + while( *v2 == '0' ) ++ v2; + } + return strcmp(v1,v2) == 0; +} + +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) { + const size_t metaindex = p_cueinfo.meta_find(p_metaname); + if (metaindex == SIZE_MAX) return; + pfc::string_formatter namelocal; + build_cue_meta_name(p_metaname,p_tracknumber,namelocal); + { + const file_info_record::t_meta_value * val = p_meta.query_ptr(namelocal); + if (val == NULL) return; + file_info_record::t_meta_value::const_iterator iter = val->first(); + for(t_size valwalk = 0, valcount = p_cueinfo.meta_enum_value_count(metaindex); valwalk < valcount; ++valwalk) { + if (iter.is_empty()) return; + + if (!meta_value_equals(*iter,p_cueinfo.meta_enum_value(metaindex,valwalk), asNumber)) return; + ++iter; + } + if (!iter.is_empty()) return; + } + //success + p_meta.remove(namelocal); +} + +// Named get_tag() for backwards compat - it doesn't just get tag, it builds the intended tag from current metadata +// Called prior to file update +void embeddedcue_metadata_manager::get_tag(file_info & p_info) const { + if (!have_cuesheet()) { + m_content.query_ptr(0u)->m_info.to_info(p_info); + p_info.meta_remove_field("cuesheet"); + return; + } + + cue_creator::t_entry_list entries; + m_content.enumerate([&entries] (unsigned p_trackno,const track_record & p_record) { + if (p_trackno > 0) { + cue_creator::t_entry_list::iterator iter = entries.insert_last(); + iter->m_trackType = "AUDIO"; + iter->m_file = p_record.m_file; + iter->m_flags = p_record.m_flags; + iter->m_index_list = p_record.m_index_list; + iter->m_track_number = p_trackno; + p_record.m_info.to_info(iter->m_infos); + } + } ); + pfc::string_formatter cuesheet; + cue_creator::create(cuesheet,entries); + entries.remove_all(); + //parse it back to see what info got stored in the cuesheet and what needs to be stored outside cuesheet in the tags + cue_parser::parse_full(cuesheet,entries); + file_info_record output; + + { + file_info_record::t_meta_map globals; + //1. find global infos and forward them + { + field_name_list fields; + { __get_tag__enum_fields_enumerator e(fields); m_content.enumerate(e);} + { __get_tag__filter_globals e(m_content,globals); fields.enumerate(e); } + } + + output.overwrite_meta(globals); + + //2. find local infos + {__get_tag__local_field_filter e(globals,output.m_meta); m_content.enumerate(e);} + } + + + //strip redundant titles, artists and tracknumbers that the cuesheet already contains + for(cue_creator::t_entry_list::const_iterator iter = entries.first(); iter.is_valid(); ++iter) { + strip_redundant_track_meta(iter->m_track_number,iter->m_infos,output.m_meta,"tracknumber", true); + strip_redundant_track_meta(iter->m_track_number,iter->m_infos,output.m_meta,"title", false); + strip_redundant_track_meta(iter->m_track_number,iter->m_infos,output.m_meta,"artist", false); + } + + + //add tech infos etc + + { + const track_record * rec = m_content.query_ptr((unsigned)0); + if (rec != NULL) { + output.set_length(rec->m_info.get_length()); + output.set_replaygain(rec->m_info.get_replaygain()); + output.overwrite_info(rec->m_info.m_info); + } + } + output.meta_set("cuesheet",cuesheet); + output.to_info(p_info); + + DEBUG_INFO( p_info, "get_tag" ); +} + +static bool resolve_cue_meta_name(const char * p_name,pfc::string_base & p_outname,unsigned & p_tracknumber) { + //"cue_trackNN_fieldname" + static const char header[] = "cue_track"; + if (pfc::stricmp_ascii_ex(p_name,strlen(header),header,~0) != 0) return false; + p_name += strlen(header); + if (!pfc::char_is_numeric(p_name[0]) || !pfc::char_is_numeric(p_name[1]) || p_name[2] != '_') return false; + unsigned tracknumber = pfc::atoui_ex(p_name,2); + if (tracknumber == 0) return false; + p_name += 3; + p_tracknumber = tracknumber; + p_outname = p_name; + return true; +} + +void embeddedcue_metadata_manager::set_tag(file_info const & p_info) { + + DEBUG_INFO( p_info, "set_tag" ); + + m_content.remove_all(); + + { + track_record & track0 = m_content.find_or_add((unsigned)0); + track0.m_info.from_info(p_info); + track0.m_info.m_info.set("cue_embedded","no"); + } + + + + const char * cuesheet = p_info.meta_get("cuesheet",0); + if (cuesheet == NULL) { + return; + } + + //processing order + //1. cuesheet content + //2. supplement with global metadata from the tag + //3. overwrite with local metadata from the tag + + { + cue_creator::t_entry_list entries; + try { + cue_parser::parse_full(cuesheet,entries); + } catch(exception_io_data const & e) { + console::complain("Attempting to embed an invalid cuesheet", e.what()); + return; + } + + { + const double length = p_info.get_length(); + for(cue_creator::t_entry_list::const_iterator iter = entries.first(); iter.is_valid(); ++iter ) { + if (iter->m_index_list.start() > length) { + console::info("Invalid cuesheet - index outside allowed range"); + return; + } + } + } + + for(cue_creator::t_entry_list::const_iterator iter = entries.first(); iter.is_valid(); ) { + cue_creator::t_entry_list::const_iterator next = iter; + ++next; + track_record & entry = m_content.find_or_add(iter->m_track_number); + entry.m_file = iter->m_file; + entry.m_flags = iter->m_flags; + entry.m_index_list = iter->m_index_list; + entry.m_info.from_info(iter->m_infos); + DEBUG_INFO(iter->m_infos, "set_tag cue track info" ); + entry.m_info.from_info_overwrite_info(p_info); + entry.m_info.m_info.set("cue_embedded","yes"); + double begin = entry.m_index_list.start(), end = next.is_valid() ? next->m_index_list.start() : p_info.get_length(); + if (end <= begin) throw exception_io_data(); + entry.m_info.set_length(end - begin); + iter = next; + } + } + + DEBUG_INFO( m_content, "set_tag part 1"); + + // == GLOBALS == + for(t_size metawalk = 0, metacount = p_info.meta_get_count(); metawalk < metacount; ++metawalk) { + const char * name = p_info.meta_enum_name(metawalk); + const t_size valuecount = p_info.meta_enum_value_count(metawalk); + if (valuecount > 0 && !is_reserved_meta_entry(name) && is_global_meta_entry(name)) { + m_content.enumerate( [&p_info, metawalk, name, valuecount] ( unsigned p_trackno, track_record & p_record ) { + if (p_trackno > 0) { + // Supplement, not overwrite + // 2021-02-12 fix: prefer whatever has more values + if (valuecount > p_record.m_info.meta_value_count(name)) { + p_record.m_info.transfer_meta_entry(name, p_info, metawalk); + } + } + } ); + } + } + + DEBUG_INFO( m_content, "set_tag part 2"); + + // == TRACK LOCALS == + { + pfc::string8_fastalloc namebuffer; + for(t_size metawalk = 0, metacount = p_info.meta_get_count(); metawalk < metacount; ++metawalk) { + const char * name = p_info.meta_enum_name(metawalk); + const t_size valuecount = p_info.meta_enum_value_count(metawalk); + unsigned trackno; + if (valuecount > 0 && !is_reserved_meta_entry(name) && resolve_cue_meta_name(name,namebuffer,trackno)) { + track_record * rec = m_content.query_ptr(trackno); + if (rec != NULL) { + rec->m_info.transfer_meta_entry(namebuffer,p_info,metawalk); + } + } + } + } + + DEBUG_INFO( m_content, "set_tag part 3"); +} + +void embeddedcue_metadata_manager::get_track_info(unsigned p_track,file_info & p_info) const { + const track_record * rec = m_content.query_ptr(p_track); + if (rec == NULL) throw exception_io_data(); + rec->m_info.to_info(p_info); + DEBUG_INFO( p_info, pfc::format("get_track_info(", p_track, ")" )); +} + +void embeddedcue_metadata_manager::set_track_info(unsigned p_track,file_info const & p_info) { + DEBUG_INFO( p_info, pfc::format("set_track_info(", p_track, ")" )); + track_record * rec = m_content.query_ptr(p_track); + if (rec == NULL) throw exception_io_data(); + rec->m_info.from_info_set_meta(p_info); + rec->m_info.set_replaygain(p_info.get_replaygain()); +} + +void embeddedcue_metadata_manager::query_track_offsets(unsigned p_track,double & p_begin,double & p_length) const { + const track_record * rec = m_content.query_ptr(p_track); + if (rec == NULL) throw exception_io_data(); + p_begin = rec->m_index_list.start(); + p_length = rec->m_info.get_length(); +} + +bool embeddedcue_metadata_manager::have_cuesheet() const { + return m_content.get_count() > 1; +} + +namespace { + class _remap_trackno_enumerator { + public: + _remap_trackno_enumerator(unsigned p_index) : m_countdown(p_index), m_result(0) {} + template<typename t_blah> void operator() (unsigned p_trackno,const t_blah&) { + if (p_trackno > 0 && m_result == 0) { + if (m_countdown == 0) { + m_result = p_trackno; + } else { + --m_countdown; + } + } + } + unsigned result() const {return m_result;} + private: + unsigned m_countdown; + unsigned m_result; + }; +}; + +unsigned embeddedcue_metadata_manager::remap_trackno(unsigned p_index) const { + if (have_cuesheet()) { + _remap_trackno_enumerator wrapper(p_index); + m_content.enumerate(wrapper); + return wrapper.result(); + } else { + return 0; + } +} + +t_size embeddedcue_metadata_manager::get_cue_track_count() const { + return m_content.get_count() - 1; +} + +pfc::string8 embeddedcue_metadata_manager::build_minimal_cuesheet() const { + cue_creator::t_entry_list entries; + m_content.enumerate([&entries](unsigned p_trackno, const track_record& p_record) { + if (p_trackno > 0) { + cue_creator::t_entry_list::iterator iter = entries.insert_last(); + iter->m_trackType = "AUDIO"; + iter->m_file = "Image.wav"; + iter->m_flags = p_record.m_flags; + iter->m_index_list = p_record.m_index_list; + iter->m_track_number = p_trackno; + } + }); + pfc::string_formatter cuesheet; + cue_creator::create(cuesheet, entries); + return cuesheet; +} \ No newline at end of file
