Mercurial > foo_out_sdl
comparison foosdk/sdk/foobar2000/helpers/cue_parser.h @ 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 #pragma once | |
| 2 | |
| 3 #include "cue_creator.h" | |
| 4 #include <SDK/chapterizer.h> | |
| 5 #include <SDK/input_impl.h> | |
| 6 | |
| 7 //HINT: for info on how to generate an embedded cuesheet enabled input, see the end of this header. | |
| 8 | |
| 9 | |
| 10 | |
| 11 namespace file_info_record_helper { | |
| 12 | |
| 13 class file_info_record { | |
| 14 public: | |
| 15 typedef pfc::chain_list_v2_t<pfc::string8> t_meta_value; | |
| 16 typedef pfc::map_t<pfc::string8,t_meta_value,file_info::field_name_comparator> t_meta_map; | |
| 17 typedef pfc::map_t<pfc::string8,pfc::string8,file_info::field_name_comparator> t_info_map; | |
| 18 | |
| 19 file_info_record() : m_replaygain(replaygain_info_invalid), m_length(0) {} | |
| 20 | |
| 21 replaygain_info get_replaygain() const {return m_replaygain;} | |
| 22 void set_replaygain(const replaygain_info & p_replaygain) {m_replaygain = p_replaygain;} | |
| 23 double get_length() const {return m_length;} | |
| 24 void set_length(double p_length) {m_length = p_length;} | |
| 25 | |
| 26 void reset(); | |
| 27 void from_info_overwrite_info(const file_info & p_info); | |
| 28 void from_info_overwrite_meta(const file_info & p_info); | |
| 29 void from_info_overwrite_rg(const file_info & p_info); | |
| 30 | |
| 31 template<typename t_source> | |
| 32 void overwrite_meta(const t_source & p_meta) { | |
| 33 m_meta.overwrite(p_meta); | |
| 34 } | |
| 35 template<typename t_source> | |
| 36 void overwrite_info(const t_source & p_info) { | |
| 37 m_info.overwrite(p_info); | |
| 38 } | |
| 39 | |
| 40 void merge_overwrite(const file_info & p_info); | |
| 41 void transfer_meta_entry(const char * p_name,const file_info & p_info,t_size p_index); | |
| 42 | |
| 43 void meta_set(const char * p_name,const char * p_value); | |
| 44 const t_meta_value * meta_query_ptr(const char * p_name) const; | |
| 45 | |
| 46 void from_info_set_meta(const file_info & p_info); | |
| 47 | |
| 48 void from_info(const file_info & p_info); | |
| 49 void to_info(file_info & p_info) const; | |
| 50 | |
| 51 template<typename t_callback> void enumerate_meta(t_callback & p_callback) const {m_meta.enumerate(p_callback);} | |
| 52 template<typename t_callback> void enumerate_meta(t_callback & p_callback) {m_meta.enumerate(p_callback);} | |
| 53 | |
| 54 size_t meta_value_count(const char* name) const; | |
| 55 | |
| 56 //private: | |
| 57 t_meta_map m_meta; | |
| 58 t_info_map m_info; | |
| 59 replaygain_info m_replaygain; | |
| 60 double m_length; | |
| 61 }; | |
| 62 | |
| 63 }//namespace file_info_record_helper | |
| 64 | |
| 65 | |
| 66 namespace cue_parser | |
| 67 { | |
| 68 struct cue_entry { | |
| 69 pfc::string8 m_file, m_fileType; | |
| 70 unsigned m_track_number; | |
| 71 t_cuesheet_index_list m_indexes; | |
| 72 bool isFileBinary() const {return pfc::stringEqualsI_ascii(m_fileType, "BINARY");} | |
| 73 }; | |
| 74 | |
| 75 typedef pfc::chain_list_v2_t<cue_entry> t_cue_entry_list; | |
| 76 | |
| 77 | |
| 78 PFC_DECLARE_EXCEPTION(exception_bad_cuesheet,exception_io_data,"Invalid cuesheet"); | |
| 79 | |
| 80 //! Throws exception_bad_cuesheet on failure. | |
| 81 void parse(const char *p_cuesheet,t_cue_entry_list & p_out); | |
| 82 //! Throws exception_bad_cuesheet on failure. | |
| 83 void parse_info(const char *p_cuesheet,file_info & p_info,unsigned p_index); | |
| 84 //! Throws exception_bad_cuesheet on failure. | |
| 85 void parse_full(const char * p_cuesheet,cue_creator::t_entry_list & p_out); | |
| 86 | |
| 87 | |
| 88 | |
| 89 struct track_record { | |
| 90 file_info_record_helper::file_info_record m_info; | |
| 91 pfc::string8 m_file,m_flags; | |
| 92 t_cuesheet_index_list m_index_list; | |
| 93 }; | |
| 94 | |
| 95 typedef pfc::map_t<unsigned,track_record> track_record_list; | |
| 96 | |
| 97 class embeddedcue_metadata_manager { | |
| 98 public: | |
| 99 // Named get_tag() for backwards compat - it doesn't just get tag, it builds the intended tag from current metadata | |
| 100 void get_tag(file_info & p_info) const; | |
| 101 // Imports tag read from file | |
| 102 void set_tag(file_info const & p_info); | |
| 103 | |
| 104 void get_track_info(unsigned p_track,file_info & p_info) const; | |
| 105 void set_track_info(unsigned p_track,file_info const & p_info); | |
| 106 void query_track_offsets(unsigned p_track,double & p_begin,double & p_length) const; | |
| 107 bool have_cuesheet() const; | |
| 108 unsigned remap_trackno(unsigned p_index) const; | |
| 109 t_size get_cue_track_count() const; | |
| 110 pfc::string8 build_minimal_cuesheet() const; | |
| 111 private: | |
| 112 track_record_list m_content; | |
| 113 }; | |
| 114 | |
| 115 | |
| 116 | |
| 117 | |
| 118 | |
| 119 template<typename t_base> | |
| 120 class input_wrapper_cue_t : public input_forward_static_methods<t_base> { | |
| 121 public: | |
| 122 typedef input_info_writer_v2 interface_info_writer_t; // remove_tags supplied | |
| 123 void open(service_ptr_t<file> p_filehint,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort) { | |
| 124 m_remote = filesystem::g_is_recognized_and_remote( p_path ); | |
| 125 if (m_remote && p_reason == input_open_info_write) throw exception_io_object_is_remote(); | |
| 126 m_impl.open( p_filehint, p_path, p_reason, p_abort ); | |
| 127 if (!m_remote) { | |
| 128 file_info_impl info; | |
| 129 m_impl.get_info(info, p_abort); | |
| 130 m_meta.set_tag(info); | |
| 131 } | |
| 132 } | |
| 133 | |
| 134 t_uint32 get_subsong_count() { | |
| 135 return this->expose_cuesheet() ? (uint32_t) m_meta.get_cue_track_count() : 1; | |
| 136 } | |
| 137 | |
| 138 t_uint32 get_subsong(t_uint32 p_index) { | |
| 139 return this->expose_cuesheet() ? m_meta.remap_trackno(p_index) : 0; | |
| 140 } | |
| 141 | |
| 142 void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort) { | |
| 143 if (m_remote) { | |
| 144 PFC_ASSERT(p_subsong == 0); | |
| 145 m_impl.get_info(p_info, p_abort); | |
| 146 } else { | |
| 147 m_meta.get_track_info(p_subsong,p_info); | |
| 148 } | |
| 149 } | |
| 150 | |
| 151 t_filestats get_file_stats(abort_callback& p_abort) { return get_stats2(stats2_legacy, p_abort).to_legacy(); } | |
| 152 t_filestats2 get_stats2(uint32_t f, abort_callback& a) { return m_impl.get_stats2(f, a); } | |
| 153 | |
| 154 void decode_initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort) { | |
| 155 if (p_subsong == 0) { | |
| 156 m_impl.decode_initialize(p_flags, p_abort); | |
| 157 m_decodeFrom = 0; m_decodeLength = -1; m_decodePos = 0; | |
| 158 } else { | |
| 159 double start, length; | |
| 160 _query_track_offsets(p_subsong,start,length); | |
| 161 unsigned flags2 = p_flags; | |
| 162 if (start > 0) flags2 &= ~input_flag_no_seeking; | |
| 163 flags2 &= ~input_flag_allow_inaccurate_seeking; | |
| 164 m_impl.decode_initialize(flags2, p_abort); | |
| 165 m_impl.decode_seek(start, p_abort); | |
| 166 m_decodeFrom = start; m_decodeLength = length; m_decodePos = 0; | |
| 167 } | |
| 168 } | |
| 169 | |
| 170 bool decode_run(audio_chunk & p_chunk,abort_callback & p_abort) { | |
| 171 return _run(p_chunk, NULL, p_abort); | |
| 172 } | |
| 173 | |
| 174 void decode_seek(double p_seconds,abort_callback & p_abort) { | |
| 175 if (this->m_decodeLength >= 0 && p_seconds > m_decodeLength) p_seconds = m_decodeLength; | |
| 176 m_impl.decode_seek(m_decodeFrom + p_seconds,p_abort); | |
| 177 m_decodePos = p_seconds; | |
| 178 } | |
| 179 | |
| 180 bool decode_can_seek() {return m_impl.decode_can_seek();} | |
| 181 | |
| 182 bool decode_run_raw(audio_chunk & p_chunk, mem_block_container & p_raw, abort_callback & p_abort) { | |
| 183 return _run(p_chunk, &p_raw, p_abort); | |
| 184 } | |
| 185 void set_logger(event_logger::ptr ptr) { | |
| 186 m_impl.set_logger(ptr); | |
| 187 } | |
| 188 bool flush_on_pause() { | |
| 189 return m_impl.flush_on_pause(); | |
| 190 } | |
| 191 void set_pause(bool) {} // obsolete | |
| 192 size_t extended_param(const GUID & type, size_t arg1, void * arg2, size_t arg2size) { | |
| 193 return m_impl.extended_param(type, arg1, arg2, arg2size); | |
| 194 } | |
| 195 | |
| 196 bool decode_get_dynamic_info(file_info & p_out, double & p_timestamp_delta) { | |
| 197 return m_impl.decode_get_dynamic_info(p_out, p_timestamp_delta); | |
| 198 } | |
| 199 | |
| 200 bool decode_get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta) { | |
| 201 return m_impl.decode_get_dynamic_info_track(p_out, p_timestamp_delta); | |
| 202 } | |
| 203 | |
| 204 void decode_on_idle(abort_callback & p_abort) { | |
| 205 m_impl.decode_on_idle(p_abort); | |
| 206 } | |
| 207 | |
| 208 void remove_tags(abort_callback & abort) { | |
| 209 PFC_ASSERT(!m_remote); | |
| 210 | |
| 211 pfc::string8 restoreCuesheet; | |
| 212 if (this->expose_cuesheet()) { | |
| 213 restoreCuesheet = m_meta.build_minimal_cuesheet(); | |
| 214 } | |
| 215 | |
| 216 m_impl.remove_tags( abort ); | |
| 217 | |
| 218 if (restoreCuesheet.length() > 0) { | |
| 219 // restore minimal tag | |
| 220 file_info_impl infoMinimal; infoMinimal.meta_set("cuesheet", restoreCuesheet); | |
| 221 m_impl.retag(infoMinimal, abort); | |
| 222 } | |
| 223 | |
| 224 file_info_impl info; | |
| 225 m_impl.get_info(info, abort); | |
| 226 m_meta.set_tag( info ); | |
| 227 } | |
| 228 | |
| 229 void retag_set_info(t_uint32 p_subsong,const file_info & p_info,abort_callback & p_abort) { | |
| 230 PFC_ASSERT(!m_remote); | |
| 231 if (p_subsong == 0) { | |
| 232 // Special case: Only subsong zero altered | |
| 233 m_retag0 = p_info; m_retag0_pending = true; | |
| 234 } else { | |
| 235 if (m_retag0_pending) { | |
| 236 m_meta.set_tag(m_retag0); m_retag0.reset(); | |
| 237 m_retag0_pending = false; | |
| 238 } | |
| 239 m_meta.set_track_info(p_subsong,p_info); | |
| 240 } | |
| 241 m_retag_pending = true; | |
| 242 } | |
| 243 void _retag(const file_info& info, abort_callback& abort) { | |
| 244 m_impl.retag(info, abort); | |
| 245 } | |
| 246 void retag_commit(abort_callback & p_abort) { | |
| 247 PFC_ASSERT(!m_remote); | |
| 248 if (!m_retag_pending) return; | |
| 249 | |
| 250 if (m_retag0_pending) { | |
| 251 // Special case: Only subsong zero altered | |
| 252 _retag(m_retag0, p_abort); m_retag0.reset(); | |
| 253 m_retag0_pending = false; | |
| 254 } else { | |
| 255 file_info_impl info; | |
| 256 m_meta.get_tag(info); | |
| 257 _retag(info, p_abort); | |
| 258 } | |
| 259 | |
| 260 file_info_impl info; | |
| 261 m_impl.get_info(info, p_abort); | |
| 262 m_meta.set_tag( info ); | |
| 263 | |
| 264 m_retag_pending = false; | |
| 265 | |
| 266 } | |
| 267 void _query_track_offsets(unsigned p_subsong, double& start, double& length) const { | |
| 268 m_meta.query_track_offsets(p_subsong,start,length); | |
| 269 } | |
| 270 bool expose_cuesheet() const { | |
| 271 return !m_remote && m_meta.have_cuesheet(); | |
| 272 } | |
| 273 private: | |
| 274 bool _run(audio_chunk & chunk, mem_block_container * raw, abort_callback & aborter) { | |
| 275 if (m_decodeLength >= 0 && m_decodePos >= m_decodeLength) return false; | |
| 276 if (raw == NULL) { | |
| 277 if (!m_impl.decode_run(chunk, aborter)) return false; | |
| 278 } else { | |
| 279 if (!m_impl.decode_run_raw(chunk, *raw, aborter)) return false; | |
| 280 } | |
| 281 | |
| 282 if (m_decodeLength >= 0) { | |
| 283 const uint64_t remaining = audio_math::time_to_samples( m_decodeLength - m_decodePos, chunk.get_sample_rate() ); | |
| 284 const size_t samplesGot = chunk.get_sample_count(); | |
| 285 if (remaining < samplesGot) { | |
| 286 m_decodePos = m_decodeLength; | |
| 287 if (remaining == 0) { // rare but possible as a result of rounding SNAFU - we're EOF but we didn't notice earlier | |
| 288 return false; | |
| 289 } | |
| 290 | |
| 291 chunk.set_sample_count( (size_t) remaining ); | |
| 292 if (raw != NULL) { | |
| 293 const t_size rawSize = raw->get_size(); | |
| 294 PFC_ASSERT( rawSize % samplesGot == 0 ); | |
| 295 raw->set_size( (t_size) ( (t_uint64) rawSize * remaining / samplesGot ) ); | |
| 296 } | |
| 297 } else { | |
| 298 m_decodePos += chunk.get_duration(); | |
| 299 } | |
| 300 } else { | |
| 301 m_decodePos += chunk.get_duration(); | |
| 302 } | |
| 303 return true; | |
| 304 } | |
| 305 t_base m_impl; | |
| 306 double m_decodeFrom, m_decodeLength, m_decodePos; | |
| 307 bool m_remote = false; | |
| 308 embeddedcue_metadata_manager m_meta; | |
| 309 | |
| 310 file_info_impl m_retag0; | |
| 311 bool m_retag0_pending = false; | |
| 312 bool m_retag_pending = false; | |
| 313 }; | |
| 314 #ifdef FOOBAR2000_HAVE_CHAPTERIZER | |
| 315 template<typename I> | |
| 316 class chapterizer_impl_t : public chapterizer | |
| 317 { | |
| 318 public: | |
| 319 bool is_our_path(const char * p_path) { | |
| 320 return I::g_is_our_path(p_path, pfc::string_extension(p_path)); | |
| 321 } | |
| 322 | |
| 323 void set_chapters(const char * p_path,chapter_list const & p_list,abort_callback & p_abort) { | |
| 324 input_wrapper_cue_t<I> instance; | |
| 325 instance.open(0,p_path,input_open_info_write,p_abort); | |
| 326 | |
| 327 //stamp the cuesheet first | |
| 328 { | |
| 329 file_info_impl info; | |
| 330 instance.get_info(0,info,p_abort); | |
| 331 | |
| 332 pfc::string_formatter cuesheet; | |
| 333 | |
| 334 { | |
| 335 cue_creator::t_entry_list entries; | |
| 336 t_size n, m = p_list.get_chapter_count(); | |
| 337 const double pregap = p_list.get_pregap(); | |
| 338 double offset_acc = pregap; | |
| 339 for(n=0;n<m;n++) | |
| 340 { | |
| 341 cue_creator::t_entry_list::iterator entry; | |
| 342 entry = entries.insert_last(); | |
| 343 entry->m_infos = p_list.get_info(n); | |
| 344 entry->m_file = "CDImage.wav"; | |
| 345 entry->m_track_number = (unsigned)(n+1); | |
| 346 entry->m_index_list.from_infos(entry->m_infos,offset_acc); | |
| 347 if (n == 0) entry->m_index_list.m_positions[0] = 0; | |
| 348 offset_acc += entry->m_infos.get_length(); | |
| 349 } | |
| 350 cue_creator::create(cuesheet,entries); | |
| 351 } | |
| 352 | |
| 353 info.meta_set("cuesheet",cuesheet); | |
| 354 | |
| 355 instance.retag_set_info(0,info,p_abort); | |
| 356 } | |
| 357 //stamp per-chapter infos | |
| 358 for(t_size walk = 0, total = p_list.get_chapter_count(); walk < total; ++walk) { | |
| 359 instance.retag_set_info( (uint32_t)( walk + 1 ), p_list.get_info(walk),p_abort); | |
| 360 } | |
| 361 | |
| 362 instance.retag_commit(p_abort); | |
| 363 } | |
| 364 | |
| 365 void get_chapters(const char * p_path,chapter_list & p_list,abort_callback & p_abort) { | |
| 366 | |
| 367 input_wrapper_cue_t<I> instance; | |
| 368 instance.open(0,p_path,input_open_info_read,p_abort); | |
| 369 const t_uint32 total = instance.get_subsong_count(); | |
| 370 | |
| 371 if (instance.expose_cuesheet()) { | |
| 372 double start, len; | |
| 373 instance._query_track_offsets(1, start, len); | |
| 374 p_list.set_pregap( start ); | |
| 375 } | |
| 376 | |
| 377 | |
| 378 p_list.set_chapter_count(total); | |
| 379 for(t_uint32 walk = 0; walk < total; ++walk) { | |
| 380 file_info_impl info; | |
| 381 instance.get_info(instance.get_subsong(walk),info,p_abort); | |
| 382 p_list.set_info(walk,info); | |
| 383 } | |
| 384 } | |
| 385 | |
| 386 bool supports_pregaps() { | |
| 387 return true; | |
| 388 } | |
| 389 }; | |
| 390 #endif | |
| 391 }; | |
| 392 | |
| 393 //! Wrapper template for generating embedded cuesheet enabled inputs. | |
| 394 //! t_input_impl is a singletrack input implementation (see input_singletrack_impl for method declarations). | |
| 395 //! To declare an embedded cuesheet enabled input, change your input declaration from input_singletrack_factory_t<myinput> to input_cuesheet_factory_t<myinput>. | |
| 396 template<typename t_input_impl, unsigned t_flags = 0> | |
| 397 class input_cuesheet_factory_t { | |
| 398 public: | |
| 399 input_factory_t<cue_parser::input_wrapper_cue_t<t_input_impl>,t_flags > m_input_factory; | |
| 400 #ifdef FOOBAR2000_HAVE_CHAPTERIZER | |
| 401 service_factory_single_t<cue_parser::chapterizer_impl_t<t_input_impl> > m_chapterizer_factory; | |
| 402 #endif | |
| 403 }; |
