comparison foosdk/sdk/foobar2000/SDK/file_info.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 "foobar2000-sdk-pch.h"
2 #include "file_info.h"
3 #include "console.h"
4 #include "filesystem.h"
5
6 #include <pfc/unicode-normalize.h>
7 #ifndef _MSC_VER
8 #define strcat_s strcat
9 #define _atoi64 atoll
10 #endif
11
12 static constexpr char info_WAVEFORMATEXTENSIBLE_CHANNEL_MASK[] = "WAVEFORMATEXTENSIBLE_CHANNEL_MASK";
13
14 t_size file_info::meta_find_ex(const char * p_name,t_size p_name_length) const
15 {
16 t_size n, m = meta_get_count();
17 for(n=0;n<m;n++)
18 {
19 if (pfc::stricmp_ascii_ex(meta_enum_name(n),SIZE_MAX,p_name,p_name_length) == 0) return n;
20 }
21 return SIZE_MAX;
22 }
23
24 bool file_info::meta_exists_ex(const char * p_name,t_size p_name_length) const
25 {
26 return meta_find_ex(p_name,p_name_length) != SIZE_MAX;
27 }
28
29 void file_info::meta_remove_field_ex(const char * p_name,t_size p_name_length)
30 {
31 auto index = meta_find_ex(p_name,p_name_length);
32 if (index!= SIZE_MAX) meta_remove_index(index);
33 }
34
35
36 void file_info::meta_remove_index(t_size p_index)
37 {
38 meta_remove_mask(pfc::bit_array_one(p_index));
39 }
40
41 void file_info::meta_remove_all()
42 {
43 meta_remove_mask(pfc::bit_array_true());
44 }
45
46 void file_info::meta_remove_value(t_size p_index,t_size p_value)
47 {
48 meta_remove_values(p_index, pfc::bit_array_one(p_value));
49 }
50
51 t_size file_info::meta_get_count_by_name_ex(const char * p_name,t_size p_name_length) const
52 {
53 auto index = meta_find_ex(p_name,p_name_length);
54 if (index == SIZE_MAX) return 0;
55 return meta_enum_value_count(index);
56 }
57
58 t_size file_info::info_find_ex(const char * p_name,t_size p_name_length) const
59 {
60 t_size n, m = info_get_count();
61 for(n=0;n<m;n++) {
62 if (pfc::stricmp_ascii_ex(info_enum_name(n), SIZE_MAX,p_name,p_name_length) == 0) return n;
63 }
64 return SIZE_MAX;
65 }
66
67 bool file_info::info_exists_ex(const char * p_name,t_size p_name_length) const
68 {
69 return info_find_ex(p_name,p_name_length) != SIZE_MAX;
70 }
71
72 void file_info::info_remove_index(t_size p_index)
73 {
74 info_remove_mask(pfc::bit_array_one(p_index));
75 }
76
77 void file_info::info_remove_all()
78 {
79 info_remove_mask(pfc::bit_array_true());
80 }
81
82 bool file_info::info_remove_ex(const char * p_name,t_size p_name_length)
83 {
84 auto index = info_find_ex(p_name,p_name_length);
85 if (index != SIZE_MAX)
86 {
87 info_remove_index(index);
88 return true;
89 }
90 else return false;
91 }
92
93 void file_info::overwrite_meta(const file_info & p_source) {
94 const t_size total = p_source.meta_get_count();
95 for(t_size walk = 0; walk < total; ++walk) {
96 copy_meta_single(p_source, walk);
97 }
98 }
99
100 bool file_info::overwrite_meta_if_changed( const file_info & source ) {
101 const t_size total = source.meta_get_count();
102 bool changed = false;
103 for(t_size walk = 0; walk < total; ++walk) {
104 auto name = source.meta_enum_name(walk);
105 auto idx = this->meta_find(name);
106 if ( idx != SIZE_MAX ) {
107 if (field_value_equals(*this, idx, source, walk)) continue;
108 }
109
110 copy_meta_single(source, walk);
111 changed = true;
112 }
113 return changed;
114 }
115
116 void file_info::copy_meta_single(const file_info & p_source,t_size p_index)
117 {
118 copy_meta_single_rename(p_source,p_index,p_source.meta_enum_name(p_index));
119 }
120
121 void file_info::copy_meta_single_nocheck(const file_info & p_source,t_size p_index)
122 {
123 const char * name = p_source.meta_enum_name(p_index);
124 t_size n, m = p_source.meta_enum_value_count(p_index);
125 t_size new_index = SIZE_MAX;
126 for(n=0;n<m;n++)
127 {
128 const char * value = p_source.meta_enum_value(p_index,n);
129 if (n == 0) new_index = meta_set_nocheck(name,value);
130 else meta_add_value(new_index,value);
131 }
132 }
133
134 void file_info::copy_meta_single_by_name_ex(const file_info & p_source,const char * p_name,t_size p_name_length)
135 {
136 auto index = p_source.meta_find_ex(p_name,p_name_length);
137 if (index != SIZE_MAX) copy_meta_single(p_source,index);
138 }
139
140 void file_info::copy_info_single_by_name_ex(const file_info & p_source,const char * p_name,t_size p_name_length)
141 {
142 auto index = p_source.info_find_ex(p_name,p_name_length);
143 if (index != SIZE_MAX) copy_info_single(p_source,index);
144 }
145
146 void file_info::copy_meta_single_by_name_nocheck_ex(const file_info & p_source,const char * p_name,t_size p_name_length)
147 {
148 auto index = p_source.meta_find_ex(p_name,p_name_length);
149 if (index != SIZE_MAX) copy_meta_single_nocheck(p_source,index);
150 }
151
152 void file_info::copy_info_single_by_name_nocheck_ex(const file_info & p_source,const char * p_name,t_size p_name_length)
153 {
154 auto index = p_source.info_find_ex(p_name,p_name_length);
155 if (index != SIZE_MAX) copy_info_single_nocheck(p_source,index);
156 }
157
158 void file_info::copy_info_single(const file_info & p_source,t_size p_index)
159 {
160 info_set(p_source.info_enum_name(p_index),p_source.info_enum_value(p_index));
161 }
162
163 void file_info::copy_info_single_nocheck(const file_info & p_source,t_size p_index)
164 {
165 info_set_nocheck(p_source.info_enum_name(p_index),p_source.info_enum_value(p_index));
166 }
167
168 void file_info::copy_meta(const file_info & p_source)
169 {
170 if (&p_source != this) {
171 meta_remove_all();
172 t_size n, m = p_source.meta_get_count();
173 for(n=0;n<m;n++)
174 copy_meta_single_nocheck(p_source,n);
175 }
176 }
177
178 void file_info::copy_info(const file_info & p_source)
179 {
180 if (&p_source != this) {
181 info_remove_all();
182 t_size n, m = p_source.info_get_count();
183 for(n=0;n<m;n++)
184 copy_info_single_nocheck(p_source,n);
185 }
186 }
187
188 void file_info::copy(const file_info & p_source)
189 {
190 if (&p_source != this) {
191 copy_meta(p_source);
192 copy_info(p_source);
193 set_length(p_source.get_length());
194 set_replaygain(p_source.get_replaygain());
195 }
196 }
197
198
199 const char * file_info::meta_get_ex(const char * p_name,t_size p_name_length,t_size p_index) const
200 {
201 auto index = meta_find_ex(p_name,p_name_length);
202 if (index == SIZE_MAX) return 0;
203 auto max = meta_enum_value_count(index);
204 if (p_index >= max) return 0;
205 return meta_enum_value(index,p_index);
206 }
207
208 const char * file_info::info_get_ex(const char * p_name,t_size p_name_length) const
209 {
210 auto index = info_find_ex(p_name,p_name_length);
211 if (index == SIZE_MAX) return 0;
212 return info_enum_value(index);
213 }
214
215 t_int64 file_info::info_get_int(const char * name) const
216 {
217 PFC_ASSERT(pfc::is_valid_utf8(name));
218 const char * val = info_get(name);
219 if (val==0) return 0;
220 return _atoi64(val);
221 }
222
223 t_int64 file_info::info_get_length_samples() const
224 {
225 t_int64 ret = 0;
226 double len = get_length();
227 t_int64 srate = info_get_int("samplerate");
228
229 if (srate>0 && len>0)
230 {
231 ret = audio_math::time_to_samples(len,(unsigned)srate);
232 }
233 return ret;
234 }
235
236 double file_info::info_get_float(const char * name) const
237 {
238 const char * ptr = info_get(name);
239 if (ptr) return pfc::string_to_float(ptr);
240 else return 0;
241 }
242
243 void file_info::info_set_int(const char * name,t_int64 value)
244 {
245 PFC_ASSERT(pfc::is_valid_utf8(name));
246 info_set(name,pfc::format_int(value));
247 }
248
249 void file_info::info_set_float(const char * name,double value,unsigned precision,bool force_sign,const char * unit)
250 {
251 PFC_ASSERT(pfc::is_valid_utf8(name));
252 PFC_ASSERT(unit==0 || strlen(unit) <= 64);
253 char temp[128];
254 pfc::float_to_string(temp,64,value,precision,force_sign);
255 temp[63] = 0;
256 if (unit)
257 {
258 strcat_s(temp," ");
259 strcat_s(temp,unit);
260 }
261 info_set(name,temp);
262 }
263
264
265 void file_info::info_set_replaygain_album_gain(float value)
266 {
267 replaygain_info temp = get_replaygain();
268 temp.m_album_gain = value;
269 set_replaygain(temp);
270 }
271
272 void file_info::info_set_replaygain_album_peak(float value)
273 {
274 replaygain_info temp = get_replaygain();
275 temp.m_album_peak = value;
276 set_replaygain(temp);
277 }
278
279 void file_info::info_set_replaygain_track_gain(float value)
280 {
281 replaygain_info temp = get_replaygain();
282 temp.m_track_gain = value;
283 set_replaygain(temp);
284 }
285
286 void file_info::info_set_replaygain_track_peak(float value)
287 {
288 replaygain_info temp = get_replaygain();
289 temp.m_track_peak = value;
290 set_replaygain(temp);
291 }
292
293
294 static bool is_valid_bps(t_int64 val)
295 {
296 return val>0 && val<=256;
297 }
298
299 unsigned file_info::info_get_decoded_bps() const
300 {
301 t_int64 val = info_get_int("decoded_bitspersample");
302 if (is_valid_bps(val)) return (unsigned)val;
303 val = info_get_int("bitspersample");
304 if (is_valid_bps(val)) return (unsigned)val;
305 return 0;
306 }
307
308 bool file_info::info_get_codec_long(pfc::string_base& out, const char * delim) const {
309 const char * codec;
310 codec = this->info_get("codec_long");
311 if (codec != nullptr) {
312 out = codec; return true;
313 }
314 codec = this->info_get("codec");
315 if (codec != nullptr) {
316 out = codec;
317 const char * profile = this->info_get("codec_profile");
318 if (profile != nullptr) {
319 out << delim << profile;
320 }
321 return true;
322 }
323 return false;
324 }
325
326 void file_info::reset()
327 {
328 info_remove_all();
329 meta_remove_all();
330 set_length(0);
331 reset_replaygain();
332 }
333
334 void file_info::reset_replaygain()
335 {
336 replaygain_info temp;
337 temp.reset();
338 set_replaygain(temp);
339 }
340
341 void file_info::copy_meta_single_rename_ex(const file_info & p_source,t_size p_index,const char * p_new_name,t_size p_new_name_length)
342 {
343 t_size n, m = p_source.meta_enum_value_count(p_index);
344 t_size new_index = SIZE_MAX;
345 for(n=0;n<m;n++)
346 {
347 const char * value = p_source.meta_enum_value(p_index,n);
348 if (n == 0) new_index = meta_set_ex(p_new_name,p_new_name_length,value,SIZE_MAX);
349 else meta_add_value(new_index,value);
350 }
351 }
352
353 t_size file_info::meta_add_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length)
354 {
355 auto index = meta_find_ex(p_name,p_name_length);
356 if (index == SIZE_MAX) return meta_set_nocheck_ex(p_name,p_name_length,p_value,p_value_length);
357 else
358 {
359 meta_add_value_ex(index,p_value,p_value_length);
360 return index;
361 }
362 }
363
364 void file_info::meta_add_value_ex(t_size p_index,const char * p_value,t_size p_value_length)
365 {
366 meta_insert_value_ex(p_index,meta_enum_value_count(p_index),p_value,p_value_length);
367 }
368
369
370 t_size file_info::meta_calc_total_value_count() const
371 {
372 t_size n, m = meta_get_count(), ret = 0;
373 for(n=0;n<m;n++) ret += meta_enum_value_count(n);
374 return ret;
375 }
376
377 bool file_info::info_set_replaygain_ex(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len)
378 {
379 replaygain_info temp = get_replaygain();
380 if (temp.set_from_meta_ex(p_name,p_name_len,p_value,p_value_len))
381 {
382 set_replaygain(temp);
383 return true;
384 }
385 else return false;
386 }
387
388 void file_info::info_set_replaygain_auto_ex(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len)
389 {
390 if (!info_set_replaygain_ex(p_name,p_name_len,p_value,p_value_len))
391 info_set_ex(p_name,p_name_len,p_value,p_value_len);
392 }
393
394 static bool _matchGain(float g1, float g2) {
395 if (g1 == replaygain_info::gain_invalid && g2 == replaygain_info::gain_invalid) return true;
396 else if (g1 == replaygain_info::gain_invalid || g2 == replaygain_info::gain_invalid) return false;
397 else return fabs(g1-g2) < 0.1;
398 }
399 static bool _matchPeak(float p1, float p2) {
400 if (p1 == replaygain_info::peak_invalid && p2 == replaygain_info::peak_invalid) return true;
401 else if (p1 == replaygain_info::peak_invalid || p2 == replaygain_info::peak_invalid) return false;
402 else return fabs(p1-p2) < 0.01;
403 }
404 bool replaygain_info::g_equalLoose( const replaygain_info & i1, const replaygain_info & i2) {
405 return _matchGain(i1.m_track_gain, i2.m_track_gain) && _matchGain(i1.m_album_gain, i2.m_album_gain) && _matchPeak(i1.m_track_peak, i2.m_track_peak) && _matchPeak(i1.m_album_peak, i2.m_album_peak);
406 }
407 bool replaygain_info::g_equal(const replaygain_info & item1,const replaygain_info & item2)
408 {
409 return item1.m_album_gain == item2.m_album_gain &&
410 item1.m_track_gain == item2.m_track_gain &&
411 item1.m_album_peak == item2.m_album_peak &&
412 item1.m_track_peak == item2.m_track_peak;
413 }
414
415 void replaygain_info::adjust(double deltaDB) {
416 if (this->is_album_gain_present()) this->m_album_gain -= (float)deltaDB;
417 if (this->is_track_gain_present()) this->m_track_gain -= (float)deltaDB;
418 const auto scale = audio_math::gain_to_scale(deltaDB);
419 if (this->is_album_peak_present()) this->m_album_peak *= (float)scale;
420 if (this->is_track_peak_present()) this->m_track_peak *= (float)scale;
421 }
422
423 bool file_info::are_meta_fields_identical(t_size p_index1,t_size p_index2) const
424 {
425 const t_size count = meta_enum_value_count(p_index1);
426 if (count != meta_enum_value_count(p_index2)) return false;
427 t_size n;
428 for(n=0;n<count;n++)
429 {
430 if (strcmp(meta_enum_value(p_index1,n),meta_enum_value(p_index2,n))) return false;
431 }
432 return true;
433 }
434
435
436 void file_info::meta_format_entry(t_size index, pfc::string_base & out, const char * separator) const {
437 out.reset();
438 t_size val, count = meta_enum_value_count(index);
439 PFC_ASSERT( count > 0);
440 for(val=0;val<count;val++)
441 {
442 if (val > 0) out += separator;
443 out += meta_enum_value(index,val);
444 }
445 }
446
447 bool file_info::meta_format(const char * p_name,pfc::string_base & p_out, const char * separator) const {
448 p_out.reset();
449 auto index = meta_find(p_name);
450 if (index == SIZE_MAX) return false;
451 meta_format_entry(index, p_out, separator);
452 return true;
453 }
454
455 void file_info::info_calculate_bitrate(uint64_t p_filesize,double p_length)
456 {
457 unsigned b = audio_math::bitrate_kbps( p_filesize, p_length );
458 if ( b > 0 ) info_set_bitrate(b);
459 }
460
461 void file_info::info_set_bitspersample(uint32_t val, bool isFloat) {
462 // Bits per sample semantics
463 // "bitspersample" is set to integer value of bits per sample
464 // "bitspersample_extra" is used for bps of 32 or 64, either "floating-point" or "fixed-point"
465 // bps other than 32 or 64 are implicitly fixed-point as floating-point for such makes no sense
466
467 info_set_int("bitspersample", val);
468 if ( isFloat || val == 32 || val == 64 ) {
469 info_set("bitspersample_extra", isFloat ? "floating-point" : "fixed-point");
470 } else {
471 info_remove("bitspersample_extra");
472 }
473 }
474
475 bool file_info::is_encoding_float() const {
476 auto bs = info_get_int("bitspersample");
477 auto extra = info_get("bitspersample_extra");
478 if (bs == 32 || bs == 64) {
479 if (extra == nullptr || strcmp(extra, "floating-point") == 0) return true;
480 }
481 return false;
482 }
483
484 bool file_info::is_encoding_overkill() const {
485 #if audio_sample_size == 32
486 auto bs = info_get_int("bitspersample");
487 auto extra = info_get("bitspersample_extra");
488 if ( bs <= 24 ) return false; // fixedpoint up to 24bit, OK
489 if ( bs > 32 ) return true; // fixed or float beyond 32bit, overkill
490
491 if ( extra != nullptr ) {
492 if (strcmp(extra, "fixed-point") == 0) return true; // int32, overkill
493 }
494 #endif
495 return false;
496 }
497
498 bool file_info::is_encoding_lossy() const {
499 const char * encoding = info_get("encoding");
500 if (encoding != NULL) {
501 if (pfc::stricmp_ascii(encoding,"lossy") == 0 /*|| pfc::stricmp_ascii(encoding,"hybrid") == 0*/) return true;
502 } else {
503 //the old way
504 //disabled: don't whine if we're not sure what we're dealing with - might be a file with info not-yet-loaded in oddball cases or a mod file
505 //if (info_get("bitspersample") == NULL) return true;
506 }
507 return false;
508 }
509
510 bool file_info::is_encoding_lossless() const {
511 const char* encoding = info_get("encoding");
512 return encoding != nullptr && pfc::stringEqualsI_ascii(encoding, "lossless");
513 }
514
515 bool file_info::g_is_meta_equal(const file_info & p_item1,const file_info & p_item2) {
516 const t_size count = p_item1.meta_get_count();
517 if (count != p_item2.meta_get_count()) {
518 //uDebugLog() << "meta count mismatch";
519 return false;
520 }
521 pfc::map_t<const char*,t_size,field_name_comparator> item2_meta_map;
522 for(t_size n=0; n<count; n++) {
523 item2_meta_map.set(p_item2.meta_enum_name(n),n);
524 }
525 for(t_size n1=0; n1<count; n1++) {
526 t_size n2;
527 if (!item2_meta_map.query(p_item1.meta_enum_name(n1),n2)) {
528 //uDebugLog() << "item2 doesn't have " << p_item1.meta_enum_name(n1);
529 return false;
530 }
531 t_size value_count = p_item1.meta_enum_value_count(n1);
532 if (value_count != p_item2.meta_enum_value_count(n2)) {
533 //uDebugLog() << "meta value count mismatch: " << p_item1.meta_enum_name(n1) << " : " << value_count << " vs " << p_item2.meta_enum_value_count(n2);
534 return false;
535 }
536 for(t_size v = 0; v < value_count; v++) {
537 if (strcmp(p_item1.meta_enum_value(n1,v),p_item2.meta_enum_value(n2,v)) != 0) {
538 //uDebugLog() << "meta mismatch: " << p_item1.meta_enum_name(n1) << " : " << p_item1.meta_enum_value(n1,v) << " vs " << p_item2.meta_enum_value(n2,v);
539 return false;
540 }
541 }
542 }
543 return true;
544 }
545
546 bool file_info::g_is_meta_equal_debug(const file_info & p_item1,const file_info & p_item2) {
547 const t_size count = p_item1.meta_get_count();
548 if (count != p_item2.meta_get_count()) {
549 FB2K_DebugLog() << "meta count mismatch";
550 return false;
551 }
552 pfc::map_t<const char*,t_size,field_name_comparator> item2_meta_map;
553 for(t_size n=0; n<count; n++) {
554 item2_meta_map.set(p_item2.meta_enum_name(n),n);
555 }
556 for(t_size n1=0; n1<count; n1++) {
557 t_size n2;
558 if (!item2_meta_map.query(p_item1.meta_enum_name(n1),n2)) {
559 FB2K_DebugLog() << "item2 doesn't have " << p_item1.meta_enum_name(n1);
560 return false;
561 }
562 t_size value_count = p_item1.meta_enum_value_count(n1);
563 if (value_count != p_item2.meta_enum_value_count(n2)) {
564 FB2K_DebugLog() << "meta value count mismatch: " << p_item1.meta_enum_name(n1) << " : " << (uint32_t)value_count << " vs " << (uint32_t)p_item2.meta_enum_value_count(n2);
565 return false;
566 }
567 for(t_size v = 0; v < value_count; v++) {
568 if (strcmp(p_item1.meta_enum_value(n1,v),p_item2.meta_enum_value(n2,v)) != 0) {
569 FB2K_DebugLog() << "meta mismatch: " << p_item1.meta_enum_name(n1) << " : " << p_item1.meta_enum_value(n1,v) << " vs " << p_item2.meta_enum_value(n2,v);
570 return false;
571 }
572 }
573 }
574 return true;
575 }
576
577 bool file_info::g_is_info_equal(const file_info & p_item1,const file_info & p_item2) {
578 t_size count = p_item1.info_get_count();
579 if (count != p_item2.info_get_count()) {
580 //uDebugLog() << "info count mismatch";
581 return false;
582 }
583 for(t_size n1=0; n1<count; n1++) {
584 t_size n2 = p_item2.info_find(p_item1.info_enum_name(n1));
585 if (n2 == SIZE_MAX) {
586 //uDebugLog() << "item2 does not have " << p_item1.info_enum_name(n1);
587 return false;
588 }
589 if (strcmp(p_item1.info_enum_value(n1),p_item2.info_enum_value(n2)) != 0) {
590 //uDebugLog() << "value mismatch: " << p_item1.info_enum_name(n1);
591 return false;
592 }
593 }
594 return true;
595 }
596
597 bool file_info::g_is_meta_subset_debug(const file_info& superset, const file_info& subset) {
598 size_t total = subset.meta_get_count();
599 bool rv = true;
600 for (size_t walk = 0; walk < total; ++walk) {
601 const char* name = subset.meta_enum_name(walk);
602 const size_t idx = superset.meta_find(name);
603 if (idx == SIZE_MAX) {
604 rv = false;
605 FB2K_console_formatter() << "Field " << name << " missing";
606 } else if (!field_value_equals(superset, idx, subset, walk)) {
607 rv = false;
608 FB2K_console_formatter() << "Field " << name << " mismatch";
609 }
610 }
611 return rv;
612 }
613
614 static bool is_valid_field_name_char(char p_char) {
615 return p_char >= 32 && p_char < 127 && p_char != '=' && p_char != '%' && p_char != '<' && p_char != '>';
616 }
617
618 bool file_info::g_is_valid_field_name(const char * p_name,t_size p_length) {
619 t_size walk;
620 for(walk = 0; walk < p_length && p_name[walk] != 0; walk++) {
621 if (!is_valid_field_name_char(p_name[walk])) return false;
622 }
623 return walk > 0;
624 }
625
626 void file_info::to_formatter(pfc::string_formatter& out) const {
627 out << "File info dump:\n";
628 if (get_length() > 0) out<< "Duration: " << pfc::format_time_ex(get_length(), 6) << "\n";
629 pfc::string_formatter temp;
630 for(t_size metaWalk = 0; metaWalk < meta_get_count(); ++metaWalk) {
631 meta_format_entry(metaWalk, temp);
632 out << "Meta: " << meta_enum_name(metaWalk) << " = " << temp << "\n";
633 }
634 for(t_size infoWalk = 0; infoWalk < info_get_count(); ++infoWalk) {
635 out << "Info: " << info_enum_name(infoWalk) << " = " << info_enum_value(infoWalk) << "\n";
636 }
637 auto rg = this->get_replaygain();
638 replaygain_info::t_text_buffer rgbuf;
639 if (rg.format_track_gain(rgbuf)) out << "RG track gain: " << rgbuf << "\n";
640 if (rg.format_track_peak(rgbuf)) out << "RG track peak: " << rgbuf << "\n";
641 if (rg.format_album_gain(rgbuf)) out << "RG album gain: " << rgbuf << "\n";
642 if (rg.format_album_peak(rgbuf)) out << "RG album peak: " << rgbuf << "\n";
643 }
644
645 void file_info::to_console() const {
646 FB2K_console_formatter1() << "File info dump:";
647 if (get_length() > 0) FB2K_console_formatter() << "Duration: " << pfc::format_time_ex(get_length(), 6);
648 pfc::string_formatter temp;
649 const auto numMeta = meta_get_count(), numInfo = info_get_count();
650 if (numMeta == 0) {
651 FB2K_console_formatter() << "Meta is blank";
652 } else for(t_size metaWalk = 0; metaWalk < numMeta; ++metaWalk) {
653 const char * name = meta_enum_name( metaWalk );
654 const auto valCount = meta_enum_value_count( metaWalk );
655 for ( size_t valWalk = 0; valWalk < valCount; ++valWalk ) {
656 FB2K_console_formatter() << "Meta: " << name << " = " << meta_enum_value( metaWalk, valWalk );
657 }
658
659 /*
660 meta_format_entry(metaWalk, temp);
661 FB2K_console_formatter() << "Meta: " << meta_enum_name(metaWalk) << " = " << temp;
662 */
663 }
664 if (numInfo == 0) {
665 FB2K_console_formatter() << "Info is blank";
666 } else for(t_size infoWalk = 0; infoWalk < numInfo; ++infoWalk) {
667 FB2K_console_formatter() << "Info: " << info_enum_name(infoWalk) << " = " << info_enum_value(infoWalk);
668 }
669 }
670
671 void file_info::info_set_channels(uint32_t v) {
672 this->info_set_int("channels", v);
673 }
674
675 void file_info::info_set_channels_ex(uint32_t channels, uint32_t mask) {
676 info_set_channels(channels);
677 info_set_wfx_chanMask(mask);
678 }
679
680 static bool parse_wfx_chanMask(const char* str, uint32_t& out) {
681 try {
682 if (pfc::strcmp_partial(str, "0x") != 0) return false;
683 out = pfc::atohex<uint32_t>(str + 2, strlen(str + 2));
684 return true;
685 } catch (...) { return false; }
686 }
687
688 void file_info::info_tidy_channels() {
689 const char * info = this->info_get(info_WAVEFORMATEXTENSIBLE_CHANNEL_MASK);
690 if (info != nullptr) {
691 bool keep = false;
692 uint32_t v;
693 if (parse_wfx_chanMask(info, v)) {
694 if (v != 0 && v != 3 && v != 4) {
695 // valid, not mono, not stereo
696 keep = true;
697 }
698 }
699 if (!keep) this->info_remove(info_WAVEFORMATEXTENSIBLE_CHANNEL_MASK);
700 }
701 }
702
703 void file_info::info_set_wfx_chanMask(uint32_t val) {
704 switch(val) {
705 case 0:
706 case 4:
707 case 3:
708 this->info_remove(info_WAVEFORMATEXTENSIBLE_CHANNEL_MASK);
709 break;
710 default:
711 info_set (info_WAVEFORMATEXTENSIBLE_CHANNEL_MASK, pfc::format("0x", pfc::format_hex(val) ) );
712 break;
713 }
714 }
715
716 uint32_t file_info::info_get_wfx_chanMask() const {
717 const char * str = this->info_get(info_WAVEFORMATEXTENSIBLE_CHANNEL_MASK);
718 if (str == NULL) return 0;
719 uint32_t ret;
720 if (parse_wfx_chanMask(str, ret)) return ret;
721 return 0;
722 }
723
724 bool file_info::field_is_person(const char * fieldName) {
725 return field_name_equals(fieldName, "artist") ||
726 field_name_equals(fieldName, "album artist") ||
727 field_name_equals(fieldName, "composer") ||
728 field_name_equals(fieldName, "performer") ||
729 field_name_equals(fieldName, "conductor") ||
730 field_name_equals(fieldName, "orchestra") ||
731 field_name_equals(fieldName, "ensemble") ||
732 field_name_equals(fieldName, "engineer");
733 }
734
735 bool file_info::field_is_title(const char * fieldName) {
736 return field_name_equals(fieldName, "title") || field_name_equals(fieldName, "album");
737 }
738
739
740 void file_info::to_stream( stream_writer * stream, abort_callback & abort ) const {
741 stream_writer_formatter<> out(* stream, abort );
742
743 out << this->get_length();
744
745 {
746 const auto rg = this->get_replaygain();
747 out << rg.m_track_gain << rg.m_album_gain << rg.m_track_peak << rg.m_album_peak;
748 }
749
750
751 {
752 const uint32_t metaCount = pfc::downcast_guarded<uint32_t>( this->meta_get_count() );
753 for(uint32_t metaWalk = 0; metaWalk < metaCount; ++metaWalk) {
754 const char * name = this->meta_enum_name( metaWalk );
755 if (*name) {
756 out.write_string_nullterm( this->meta_enum_name( metaWalk ) );
757 const size_t valCount = this->meta_enum_value_count( metaWalk );
758 for(size_t valWalk = 0; valWalk < valCount; ++valWalk) {
759 const char * value = this->meta_enum_value( metaWalk, valWalk );
760 if (*value) {
761 out.write_string_nullterm( value );
762 }
763 }
764 out.write_int<char>(0);
765 }
766 }
767 out.write_int<char>(0);
768 }
769
770 {
771 const uint32_t infoCount = pfc::downcast_guarded<uint32_t>( this->info_get_count() );
772 for(uint32_t infoWalk = 0; infoWalk < infoCount; ++infoWalk) {
773 const char * name = this->info_enum_name( infoWalk );
774 const char * value = this->info_enum_value( infoWalk );
775 if (*name && *value) {
776 out.write_string_nullterm(name); out.write_string_nullterm(value);
777 }
778 }
779 out.write_int<char>(0);
780 }
781 }
782
783 void file_info::from_stream( stream_reader * stream, abort_callback & abort ) {
784 stream_reader_formatter<> in( *stream, abort );
785 pfc::string_formatter tempName, tempValue;
786 {
787 double len; in >> len; this->set_length( len );
788 }
789 {
790 replaygain_info rg;
791 in >> rg.m_track_gain >> rg.m_album_gain >> rg.m_track_peak >> rg.m_album_peak;
792 }
793
794 {
795 this->meta_remove_all();
796 for(;;) {
797 in.read_string_nullterm( tempName );
798 if (tempName.length() == 0) break;
799 size_t metaIndex = SIZE_MAX;
800 for(;;) {
801 in.read_string_nullterm( tempValue );
802 if (tempValue.length() == 0) break;
803 if (metaIndex == SIZE_MAX) metaIndex = this->meta_add( tempName, tempValue );
804 else this->meta_add_value( metaIndex, tempValue );
805 }
806 }
807 }
808 {
809 this->info_remove_all();
810 for(;;) {
811 in.read_string_nullterm( tempName );
812 if (tempName.length() == 0) break;
813 in.read_string_nullterm( tempValue );
814 this->info_set( tempName, tempValue );
815 }
816 }
817 }
818
819 static const char * _readString( const uint8_t * & ptr, size_t & remaining ) {
820 const char * rv = (const char*)ptr;
821 for(;;) {
822 if (remaining == 0) throw exception_io_data();
823 uint8_t byte = *ptr++; --remaining;
824 if (byte == 0) break;
825 }
826 return rv;
827 }
828
829 template<typename int_t> void _readInt( int_t & out, const uint8_t * &ptr, size_t & remaining) {
830 if (remaining < sizeof(out)) throw exception_io_data();
831 pfc::decode_little_endian( out, ptr ); ptr += sizeof(out); remaining -= sizeof(out);
832 }
833
834 template<typename float_t> static void _readFloat(float_t & out, const uint8_t * &ptr, size_t & remaining) {
835 union {
836 typename pfc::sized_int_t<sizeof(float_t)>::t_unsigned i;
837 float_t f;
838 } u;
839 _readInt(u.i, ptr, remaining);
840 out = u.f;
841 }
842
843 void file_info::from_mem( const void * memPtr, size_t memSize ) {
844 size_t remaining = memSize;
845 const uint8_t * walk = (const uint8_t*) memPtr;
846
847 {
848 double len; _readFloat(len, walk, remaining);
849 this->set_length( len );
850 }
851
852 {
853 replaygain_info rg;
854 _readFloat(rg.m_track_gain, walk, remaining );
855 _readFloat(rg.m_album_gain, walk, remaining );
856 _readFloat(rg.m_track_peak, walk, remaining );
857 _readFloat(rg.m_album_peak, walk, remaining );
858 this->set_replaygain( rg );
859 }
860
861 {
862 this->meta_remove_all();
863 for(;;) {
864 const char * metaName = _readString( walk, remaining );
865 if (*metaName == 0) break;
866 size_t metaIndex = SIZE_MAX;
867 for(;;) {
868 const char * metaValue = _readString( walk, remaining );
869 if (*metaValue == 0) break;
870 if (metaIndex == SIZE_MAX) metaIndex = this->meta_add( metaName, metaValue );
871 else this->meta_add_value( metaIndex, metaName );
872 }
873 }
874 }
875 {
876 this->info_remove_all();
877 for(;;) {
878 const char * infoName = _readString( walk, remaining );
879 if (*infoName == 0) break;
880 const char * infoValue = _readString( walk, remaining );
881 this->info_set( infoName, infoValue );
882 }
883 }
884 }
885
886 void file_info::set_audio_chunk_spec(audio_chunk::spec_t s) {
887 this->info_set_int("samplerate", s.sampleRate);
888 this->info_set_int("channels", s.chanCount);
889 uint32_t mask = 0;
890 if (audio_chunk::g_count_channels(s.chanMask) == s.chanCount) {
891 mask = s.chanMask;
892 }
893 this->info_set_wfx_chanMask(mask); // clears if zero or one of trivial values
894 }
895
896 audio_chunk::spec_t file_info::audio_chunk_spec() const
897 {
898 audio_chunk::spec_t rv = {};
899 rv.sampleRate = (uint32_t)this->info_get_int("samplerate");
900 rv.chanCount = (uint32_t)this->info_get_int("channels");
901 rv.chanMask = (uint32_t)this->info_get_wfx_chanMask();
902 if (audio_chunk::g_count_channels( rv.chanMask ) != rv.chanCount ) {
903 rv.chanMask = audio_chunk::g_guess_channel_config( rv.chanCount );
904 }
905 return rv;
906 }
907
908 bool file_info::field_value_equals(const file_info& i1, size_t meta1, const file_info& i2, size_t meta2) {
909 const size_t c = i1.meta_enum_value_count(meta1);
910 if (c != i2.meta_enum_value_count(meta2)) return false;
911 for (size_t walk = 0; walk < c; ++walk) {
912 if (strcmp(i1.meta_enum_value(meta1, walk), i2.meta_enum_value(meta2, walk)) != 0) return false;
913 }
914 return true;
915 }
916
917 bool file_info::unicode_normalize_C() {
918 const size_t total = this->meta_get_count();
919 bool changed = false;
920 for (size_t mwalk = 0; mwalk < total; ++mwalk) {
921 const size_t totalV = this->meta_enum_value_count(mwalk);
922 for (size_t vwalk = 0; vwalk < totalV; ++vwalk) {
923 const char* val = this->meta_enum_value(mwalk, vwalk);
924 if (pfc::stringContainsFormD(val)) {
925 auto norm = pfc::unicodeNormalizeC(val);
926 if (strcmp(norm, val) != 0) {
927 this->meta_modify_value(mwalk, vwalk, norm);
928 changed = true;
929 }
930 }
931 }
932 }
933 return changed;
934 }
935
936 void file_info::meta_enumerate(meta_enumerate_t cb) const {
937 const size_t nMeta = this->meta_get_count();
938 for (size_t metaWalk = 0; metaWalk < nMeta; ++metaWalk) {
939 const char* name = this->meta_enum_name(metaWalk);
940 const size_t nValue = this->meta_enum_value_count(metaWalk);
941 for (size_t valueWalk = 0; valueWalk < nValue; ++valueWalk) {
942 const char* value = this->meta_enum_value(metaWalk, valueWalk);
943 cb(name, value);
944 }
945 }
946 }
947
948 bool file_info::meta_value_exists( const char * name, const char * findValue, bool insensitive ) const {
949 const auto idx = this->meta_find(name);
950 if ( idx != SIZE_MAX ) {
951 const auto count = this->meta_enum_value_count(idx);
952 for( size_t walk = 0; walk < count; ++ walk) {
953 auto value = this->meta_enum_value(idx, walk);
954 if ( insensitive ) {
955 if (pfc::stringEqualsI_utf8(value, findValue)) return true;
956 } else {
957 if ( strcmp(value, findValue) == 0 ) return true;
958 }
959 }
960 }
961 return false;
962 }
963
964 const char * file_info::meta_get_title( const char * fallback) const {
965 auto ret = meta_get("title", 0);
966 return ret?ret:fallback;
967 }
968
969 #ifdef FOOBAR2000_MOBILE
970 #include "album_art.h"
971 #include "hasher_md5.h"
972
973 void file_info::info_set_pictures( const GUID * guids, size_t size ) {
974 this->info_set("pictures", album_art_ids::ids_to_string(guids, size) );
975 }
976
977 pfc::array_t<GUID> file_info::info_get_pictures( ) const {
978 return album_art_ids::string_to_ids( this->info_get( "pictures" ) );
979 }
980
981 bool file_info::info_have_picture( const GUID & arg ) const {
982 for( auto & walk : info_get_pictures() ) {
983 if ( walk == arg ) return true;
984 }
985 return false;
986 }
987
988 uint64_t file_info::makeMetaHash() const {
989 pfc::string_formatter temp;
990
991 auto doMeta = [&] ( const char * meta ) {
992 const char * p = meta_get(meta, 0);
993 if (p != nullptr) temp << p;
994 temp << "\n";
995 };
996 auto doMetaInt = [&] ( const char * meta ) {
997 const char * p = meta_get(meta, 0);
998 if (p != nullptr) {
999 auto s = strchr(p, '/' ); if ( s != nullptr ) p = s+1;
1000 while(*p == '0') ++p;
1001 temp << p;
1002 }
1003 temp << "\n";
1004 };
1005 doMeta("title");
1006 doMeta("artist");
1007 doMeta("album");
1008 doMetaInt("tracknumber");
1009 doMetaInt("discnumber");
1010
1011 if (temp.length() == 5) return 0;
1012
1013 return hasher_md5::get()->process_single( temp.c_str(), temp.length( ) ).xorHalve();
1014 }
1015
1016 #endif // FOOBAR2000_MOBILE