#include "foobar2000.h" #ifndef _MSC_VER #define strcat_s strcat #define _atoi64 atoll #endif const float replaygain_info::peak_invalid = -1; const float replaygain_info::gain_invalid = -1000; t_size file_info::meta_find_ex(const char * p_name,t_size p_name_length) const { t_size n, m = meta_get_count(); for(n=0;n= max) return 0; return meta_enum_value(index,p_index); } const char * file_info::info_get_ex(const char * p_name,t_size p_name_length) const { t_size index = info_find_ex(p_name,p_name_length); if (index == pfc_infinite) return 0; return info_enum_value(index); } t_int64 file_info::info_get_int(const char * name) const { PFC_ASSERT(pfc::is_valid_utf8(name)); const char * val = info_get(name); if (val==0) return 0; return _atoi64(val); } t_int64 file_info::info_get_length_samples() const { t_int64 ret = 0; double len = get_length(); t_int64 srate = info_get_int("samplerate"); if (srate>0 && len>0) { ret = audio_math::time_to_samples(len,(unsigned)srate); } return ret; } double file_info::info_get_float(const char * name) const { const char * ptr = info_get(name); if (ptr) return pfc::string_to_float(ptr); else return 0; } void file_info::info_set_int(const char * name,t_int64 value) { PFC_ASSERT(pfc::is_valid_utf8(name)); info_set(name,pfc::format_int(value)); } void file_info::info_set_float(const char * name,double value,unsigned precision,bool force_sign,const char * unit) { PFC_ASSERT(pfc::is_valid_utf8(name)); PFC_ASSERT(unit==0 || strlen(unit) <= 64); char temp[128]; pfc::float_to_string(temp,64,value,precision,force_sign); temp[63] = 0; if (unit) { strcat_s(temp," "); strcat_s(temp,unit); } info_set(name,temp); } void file_info::info_set_replaygain_album_gain(float value) { replaygain_info temp = get_replaygain(); temp.m_album_gain = value; set_replaygain(temp); } void file_info::info_set_replaygain_album_peak(float value) { replaygain_info temp = get_replaygain(); temp.m_album_peak = value; set_replaygain(temp); } void file_info::info_set_replaygain_track_gain(float value) { replaygain_info temp = get_replaygain(); temp.m_track_gain = value; set_replaygain(temp); } void file_info::info_set_replaygain_track_peak(float value) { replaygain_info temp = get_replaygain(); temp.m_track_peak = value; set_replaygain(temp); } static bool is_valid_bps(t_int64 val) { return val>0 && val<=256; } unsigned file_info::info_get_decoded_bps() const { t_int64 val = info_get_int("decoded_bitspersample"); if (is_valid_bps(val)) return (unsigned)val; val = info_get_int("bitspersample"); if (is_valid_bps(val)) return (unsigned)val; return 0; } void file_info::reset() { info_remove_all(); meta_remove_all(); set_length(0); reset_replaygain(); } void file_info::reset_replaygain() { replaygain_info temp; temp.reset(); set_replaygain(temp); } 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) { t_size n, m = p_source.meta_enum_value_count(p_index); t_size new_index = pfc_infinite; for(n=0;n 0); for(val=0;val 0) out += separator; out += meta_enum_value(index,val); } } bool file_info::meta_format(const char * p_name,pfc::string_base & p_out, const char * separator) const { p_out.reset(); t_size index = meta_find(p_name); if (index == pfc_infinite) return false; meta_format_entry(index, p_out, separator); return true; } void file_info::info_calculate_bitrate(t_filesize p_filesize,double p_length) { unsigned b = audio_math::bitrate_kbps( p_filesize, p_length ); if ( b > 0 ) info_set_bitrate(b); } bool file_info::is_encoding_overkill() const { auto bs = info_get_int("bitspersample"); auto extra = info_get("bitspersample_extra"); if ( bs <= 24 ) return false; // fixedpoint up to 24bit, OK if ( bs > 32 ) return true; // fixed or float beyond 32bit, overkill if ( extra != nullptr ) { if (strcmp(extra, "fixed-point") == 0) return true; // int32, overkill } return false; } bool file_info::is_encoding_lossy() const { const char * encoding = info_get("encoding"); if (encoding != NULL) { if (pfc::stricmp_ascii(encoding,"lossy") == 0 /*|| pfc::stricmp_ascii(encoding,"hybrid") == 0*/) return true; } else { //the old way //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 //if (info_get("bitspersample") == NULL) return true; } return false; } bool file_info::g_is_meta_equal(const file_info & p_item1,const file_info & p_item2) { const t_size count = p_item1.meta_get_count(); if (count != p_item2.meta_get_count()) { //uDebugLog() << "meta count mismatch"; return false; } pfc::map_t item2_meta_map; for(t_size n=0; n item2_meta_map; for(t_size n=0; n= 32 && p_char < 127 && p_char != '=' && p_char != '%' && p_char != '<' && p_char != '>'; } bool file_info::g_is_valid_field_name(const char * p_name,t_size p_length) { t_size walk; for(walk = 0; walk < p_length && p_name[walk] != 0; walk++) { if (!is_valid_field_name_char(p_name[walk])) return false; } return walk > 0; } void file_info::to_formatter(pfc::string_formatter& out) const { out << "File info dump:\n"; if (get_length() > 0) out<< "Duration: " << pfc::format_time_ex(get_length(), 6) << "\n"; pfc::string_formatter temp; for(t_size metaWalk = 0; metaWalk < meta_get_count(); ++metaWalk) { meta_format_entry(metaWalk, temp); out << "Meta: " << meta_enum_name(metaWalk) << " = " << temp << "\n"; } for(t_size infoWalk = 0; infoWalk < info_get_count(); ++infoWalk) { out << "Info: " << info_enum_name(infoWalk) << " = " << info_enum_value(infoWalk) << "\n"; } auto rg = this->get_replaygain(); replaygain_info::t_text_buffer rgbuf; if (rg.format_track_gain(rgbuf)) out << "RG track gain: " << rgbuf << "\n"; if (rg.format_track_peak(rgbuf)) out << "RG track peak: " << rgbuf << "\n"; if (rg.format_album_gain(rgbuf)) out << "RG album gain: " << rgbuf << "\n"; if (rg.format_album_peak(rgbuf)) out << "RG album peak: " << rgbuf << "\n"; } void file_info::to_console() const { FB2K_console_formatter1() << "File info dump:"; if (get_length() > 0) FB2K_console_formatter() << "Duration: " << pfc::format_time_ex(get_length(), 6); pfc::string_formatter temp; for(t_size metaWalk = 0; metaWalk < meta_get_count(); ++metaWalk) { const char * name = meta_enum_name( metaWalk ); const auto valCount = meta_enum_value_count( metaWalk ); for ( size_t valWalk = 0; valWalk < valCount; ++valWalk ) { FB2K_console_formatter() << "Meta: " << name << " = " << meta_enum_value( metaWalk, valWalk ); } /* meta_format_entry(metaWalk, temp); FB2K_console_formatter() << "Meta: " << meta_enum_name(metaWalk) << " = " << temp; */ } for(t_size infoWalk = 0; infoWalk < info_get_count(); ++infoWalk) { FB2K_console_formatter() << "Info: " << info_enum_name(infoWalk) << " = " << info_enum_value(infoWalk); } } void file_info::info_set_wfx_chanMask(uint32_t val) { switch(val) { case 0: case 4: case 3: break; default: info_set ("WAVEFORMATEXTENSIBLE_CHANNEL_MASK", PFC_string_formatter() << "0x" << pfc::format_hex(val) ); break; } } uint32_t file_info::info_get_wfx_chanMask() const { const char * str = this->info_get("WAVEFORMATEXTENSIBLE_CHANNEL_MASK"); if (str == NULL) return 0; if (pfc::strcmp_partial( str, "0x") != 0) return 0; try { return pfc::atohex( str + 2, strlen(str+2) ); } catch(...) { return 0;} } bool file_info::field_is_person(const char * fieldName) { return field_name_equals(fieldName, "artist") || field_name_equals(fieldName, "album artist") || field_name_equals(fieldName, "composer") || field_name_equals(fieldName, "performer") || field_name_equals(fieldName, "conductor") || field_name_equals(fieldName, "orchestra") || field_name_equals(fieldName, "ensemble") || field_name_equals(fieldName, "engineer"); } bool file_info::field_is_title(const char * fieldName) { return field_name_equals(fieldName, "title") || field_name_equals(fieldName, "album"); } void file_info::to_stream( stream_writer * stream, abort_callback & abort ) const { stream_writer_formatter<> out(* stream, abort ); out << this->get_length(); { const auto rg = this->get_replaygain(); out << rg.m_track_gain << rg.m_album_gain << rg.m_track_peak << rg.m_album_peak; } { const uint32_t metaCount = pfc::downcast_guarded( this->meta_get_count() ); for(uint32_t metaWalk = 0; metaWalk < metaCount; ++metaWalk) { const char * name = this->meta_enum_name( metaWalk ); if (*name) { out.write_string_nullterm( this->meta_enum_name( metaWalk ) ); const size_t valCount = this->meta_enum_value_count( metaWalk ); for(size_t valWalk = 0; valWalk < valCount; ++valWalk) { const char * value = this->meta_enum_value( metaWalk, valWalk ); if (*value) { out.write_string_nullterm( value ); } } out.write_int(0); } } out.write_int(0); } { const uint32_t infoCount = pfc::downcast_guarded( this->info_get_count() ); for(uint32_t infoWalk = 0; infoWalk < infoCount; ++infoWalk) { const char * name = this->info_enum_name( infoWalk ); const char * value = this->info_enum_value( infoWalk ); if (*name && *value) { out.write_string_nullterm(name); out.write_string_nullterm(value); } } out.write_int(0); } } void file_info::from_stream( stream_reader * stream, abort_callback & abort ) { stream_reader_formatter<> in( *stream, abort ); pfc::string_formatter tempName, tempValue; { double len; in >> len; this->set_length( len ); } { replaygain_info rg; in >> rg.m_track_gain >> rg.m_album_gain >> rg.m_track_peak >> rg.m_album_peak; } { this->meta_remove_all(); for(;;) { in.read_string_nullterm( tempName ); if (tempName.length() == 0) break; size_t metaIndex = pfc_infinite; for(;;) { in.read_string_nullterm( tempValue ); if (tempValue.length() == 0) break; if (metaIndex == pfc_infinite) metaIndex = this->meta_add( tempName, tempValue ); else this->meta_add_value( metaIndex, tempValue ); } } } { this->info_remove_all(); for(;;) { in.read_string_nullterm( tempName ); if (tempName.length() == 0) break; in.read_string_nullterm( tempValue ); this->info_set( tempName, tempValue ); } } } static const char * _readString( const uint8_t * & ptr, size_t & remaining ) { const char * rv = (const char*)ptr; for(;;) { if (remaining == 0) throw exception_io_data(); uint8_t byte = *ptr++; --remaining; if (byte == 0) break; } return rv; } template void _readInt( int_t & out, const uint8_t * &ptr, size_t & remaining) { if (remaining < sizeof(out)) throw exception_io_data(); pfc::decode_little_endian( out, ptr ); ptr += sizeof(out); remaining -= sizeof(out); } template static void _readFloat(float_t & out, const uint8_t * &ptr, size_t & remaining) { union { typename pfc::sized_int_t::t_unsigned i; float_t f; } u; _readInt(u.i, ptr, remaining); out = u.f; } void file_info::from_mem( const void * memPtr, size_t memSize ) { size_t remaining = memSize; const uint8_t * walk = (const uint8_t*) memPtr; { double len; _readFloat(len, walk, remaining); this->set_length( len ); } { replaygain_info rg; _readFloat(rg.m_track_gain, walk, remaining ); _readFloat(rg.m_album_gain, walk, remaining ); _readFloat(rg.m_track_peak, walk, remaining ); _readFloat(rg.m_album_peak, walk, remaining ); this->set_replaygain( rg ); } { this->meta_remove_all(); for(;;) { const char * metaName = _readString( walk, remaining ); if (*metaName == 0) break; size_t metaIndex = pfc_infinite; for(;;) { const char * metaValue = _readString( walk, remaining ); if (*metaValue == 0) break; if (metaIndex == pfc_infinite) metaIndex = this->meta_add( metaName, metaValue ); else this->meta_add_value( metaIndex, metaName ); } } } { this->info_remove_all(); for(;;) { const char * infoName = _readString( walk, remaining ); if (*infoName == 0) break; const char * infoValue = _readString( walk, remaining ); this->info_set( infoName, infoValue ); } } } audio_chunk::spec_t file_info::audio_chunk_spec() const { audio_chunk::spec_t rv = {}; rv.sampleRate = (uint32_t)this->info_get_int("samplerate"); rv.chanCount = (uint32_t)this->info_get_int("channels"); rv.chanMask = (uint32_t)this->info_get_wfx_chanMask(); if (audio_chunk::g_count_channels( rv.chanMask ) != rv.chanCount ) { rv.chanMask = audio_chunk::g_guess_channel_config( rv.chanCount ); } return rv; }