#include "stdafx.h" #include "cue_parser.h" using namespace cue_parser; using namespace file_info_record_helper; 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 { class __get_tag_cue_track_list_builder { public: __get_tag_cue_track_list_builder(cue_creator::t_entry_list & p_entries) : m_entries(p_entries) {} void operator() (unsigned p_trackno,const track_record & p_record) { if (p_trackno > 0) { cue_creator::t_entry_list::iterator iter = m_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); } } private: cue_creator::t_entry_list & m_entries; }; typedef pfc::avltree_t 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 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::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); } void embeddedcue_metadata_manager::get_tag(file_info & p_info) const { if (!have_cuesheet()) { m_content.query_ptr((unsigned)0)->m_info.to_info(p_info); p_info.meta_remove_field("cuesheet"); } else { cue_creator::t_entry_list entries; { __get_tag_cue_track_list_builder e(entries); m_content.enumerate(e); } 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 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); } //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); } } 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; } namespace { class __set_tag_global_field_relay { public: __set_tag_global_field_relay(const file_info & p_info,t_size p_index) : m_info(p_info), m_index(p_index) {} void operator() (unsigned p_trackno,track_record & p_record) { if (p_trackno > 0) { p_record.m_info.transfer_meta_entry(m_info.meta_enum_name(m_index),m_info,m_index); } } private: const file_info & m_info; const t_size m_index; }; } void embeddedcue_metadata_manager::set_tag(file_info const & p_info) { 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. overwrite 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); 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; } } 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)) { __set_tag_global_field_relay relay(p_info,metawalk); m_content.enumerate(relay); } } { 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); } } } } } 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); } void embeddedcue_metadata_manager::set_track_info(unsigned p_track,file_info const & p_info) { 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 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; }