|
1
|
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
|