File indexing completed on 2024-05-19 04:56:09
0001 /** 0002 * \file attributedata.cpp 0003 * String representation of attribute data. 0004 * 0005 * \b Project: Kid3 0006 * \author Urs Fleisch 0007 * \date 28 Mar 2009 0008 * 0009 * Copyright (C) 2009-2024 Urs Fleisch 0010 * 0011 * This file is part of Kid3. 0012 * 0013 * Kid3 is free software; you can redistribute it and/or modify 0014 * it under the terms of the GNU General Public License as published by 0015 * the Free Software Foundation; either version 2 of the License, or 0016 * (at your option) any later version. 0017 * 0018 * Kid3 is distributed in the hope that it will be useful, 0019 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0020 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0021 * GNU General Public License for more details. 0022 * 0023 * You should have received a copy of the GNU General Public License 0024 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0025 */ 0026 0027 #include "attributedata.h" 0028 #include <QMap> 0029 0030 /** 0031 * Constructor. 0032 * 0033 * @param name owner of Windows media PRIV frame 0034 */ 0035 AttributeData::AttributeData(const QString& name) 0036 { 0037 /** PRIV-owner and type of Windows media PRIV frames */ 0038 static const struct TypeOfWmPriv { 0039 const char* str; 0040 Type type; 0041 } typeOfWmPriv[] = { 0042 { "AverageLevel", DWord }, 0043 { "PeakValue", DWord }, 0044 { "WM/AlbumArtist", Utf16 }, 0045 { "WM/AuthorURL", Utf16 }, 0046 { "WM/BeatsPerMinute", Utf16 }, 0047 { "WM/Composer", Utf16 }, 0048 { "WM/Conductor", Utf16 }, 0049 { "WM/ContentDistributor", Utf16 }, 0050 { "WM/ContentGroupDescription", Utf16 }, 0051 { "WM/EncodedBy", Utf16 }, 0052 { "WM/EncodingSettings", Utf16 }, 0053 { "WM/EncodingTime", Binary }, 0054 { "WM/Genre", Utf16 }, 0055 { "WM/InitialKey", Utf16 }, 0056 { "WM/Language", Utf16 }, 0057 { "WM/Lyrics", Utf16 }, 0058 { "WM/Lyrics_Synchronised", Binary }, 0059 { "WM/MCDI", Binary }, 0060 { "WM/MediaClassPrimaryID", Guid }, 0061 { "WM/MediaClassSecondaryID", Guid }, 0062 { "WM/Mood", Utf16 }, 0063 { "WM/ParentalRating", Utf16 }, 0064 { "WM/PartOfSet", Utf16 }, 0065 { "WM/Period", Utf16 }, 0066 { "WM/Picture", Binary }, 0067 { "WM/Producer", Utf16 }, 0068 { "WM/PromotionURL", Utf16 }, 0069 { "WM/Provider", Utf16 }, 0070 { "WM/Publisher", Utf16 }, 0071 { "WM/SubTitle", Utf16 }, 0072 { "WM/ToolName", Utf16 }, 0073 { "WM/ToolVersion", Utf16 }, 0074 { "WM/TrackNumber", Utf16 }, 0075 { "WM/UniqueFileIdentifier", Utf16 }, 0076 { "WM/UserWebURL", Binary }, 0077 { "WM/WMCollectionGroupID", Guid }, 0078 { "WM/WMCollectionID", Guid }, 0079 { "WM/WMContentID", Guid }, 0080 { "WM/Writer", Utf16 } 0081 }; 0082 0083 static QMap<QString, int> strNumMap; 0084 if (strNumMap.empty()) { 0085 // first time initialization 0086 for (const auto& [str, type] : typeOfWmPriv) { 0087 strNumMap.insert(QString::fromLatin1(str), type); 0088 } 0089 } 0090 auto it = strNumMap.constFind(name); 0091 m_type = it != strNumMap.constEnd() ? static_cast<Type>(*it) : Unknown; 0092 } 0093 0094 /** 0095 * Convert attribute data to string. 0096 * 0097 * @param data byte array with data 0098 * @param str result string 0099 * 0100 * @return true if ok. 0101 */ 0102 bool AttributeData::toString(const QByteArray& data, QString& str) const 0103 { 0104 switch (m_type) { 0105 case Utf16: { 0106 #if QT_VERSION >= 0x060000 0107 auto unicode = reinterpret_cast<const char16_t*>(data.data()); 0108 #else 0109 auto unicode = reinterpret_cast<const ushort*>(data.data()); 0110 #endif 0111 int size = data.size() / 2; 0112 while (size > 0 && unicode[size - 1] == 0) { 0113 --size; 0114 } 0115 str = QString::fromUtf16(unicode, size); 0116 return true; 0117 } 0118 case Guid: 0119 if (data.size() == 16) { 0120 str.clear(); 0121 for (int i = 0; i < 16; ++i) { 0122 if (i == 4 || i == 6 || i == 8 || i == 10) { 0123 str += QLatin1Char('-'); 0124 } 0125 auto c = static_cast<unsigned char>(data[i]); 0126 unsigned char d = c >> 4; 0127 str += QLatin1Char(d >= 10 ? d - 10 + 'A' : d + '0'); 0128 d = c & 0x0f; 0129 str += QLatin1Char(d >= 10 ? d - 10 + 'A' : d + '0'); 0130 } 0131 return true; 0132 } 0133 break; 0134 case DWord: 0135 if (data.size() == 4) { 0136 ulong num = 0; 0137 for (int i = 3; i >= 0; --i) { 0138 num <<= 8; 0139 num |= static_cast<unsigned char>(data[i]); 0140 } 0141 str.setNum(num); 0142 return true; 0143 } 0144 break; 0145 case Binary: 0146 case Unknown: 0147 ; 0148 } 0149 return false; 0150 } 0151 0152 /** 0153 * Convert attribute data string to byte array. 0154 * 0155 * @param str string representation of data 0156 * @param data result data 0157 * 0158 * @return true if ok. 0159 */ 0160 bool AttributeData::toByteArray(const QString& str, QByteArray& data) const 0161 { 0162 switch (m_type) { 0163 case Utf16: { 0164 const ushort* unicode = str.utf16(); 0165 data = QByteArray(reinterpret_cast<const char*>(unicode), 0166 (str.length() + 1) * 2); 0167 return true; 0168 } 0169 case Guid: { 0170 QString hexStr(str.toUpper()); 0171 hexStr.remove(QLatin1Char('-')); 0172 if (hexStr.length() == 32) { 0173 unsigned char buf[16]; 0174 unsigned char* bufPtr = buf; 0175 for (int i = 0; i < 32;) { 0176 auto h = static_cast<unsigned char>(hexStr[i++].toLatin1()); 0177 auto l = static_cast<unsigned char>(hexStr[i++].toLatin1()); 0178 if (!((h >= '0' && h <= '9') || (h >= 'A' && h <= 'F')) || 0179 !((l >= '0' && l <= '9') || (l >= 'A' && l <= 'F'))) { 0180 return false; 0181 } 0182 *bufPtr++ = ((h >= 'A' ? h + 10 - 'A' : h - '0') << 4) | 0183 (l >= 'A' ? l + 10 - 'A' : l - '0'); 0184 } 0185 data = QByteArray(reinterpret_cast<char*>(buf), 16); 0186 return true; 0187 } 0188 break; 0189 } 0190 case DWord: { 0191 bool ok; 0192 ulong num = str.toULong(&ok); 0193 if (ok) { 0194 data.resize(4); 0195 for (int i = 0; i < 4; ++i) { 0196 data[i] = num & 0xff; 0197 num >>= 8; 0198 } 0199 return true; 0200 } 0201 break; 0202 } 0203 case Binary: 0204 case Unknown: 0205 ; 0206 } 0207 return false; 0208 } 0209 0210 /** 0211 * Check if a string represents a hexadecimal number, i.e. 0212 * contains only characters 0..9, A..F, a..f. 0213 * 0214 * @param str string to check 0215 * @param lastAllowedLetter last allowed character (normally 'F') 0216 * @param additionalChars additional allowed characters 0217 * 0218 * @return true if string has hex format. 0219 */ 0220 bool AttributeData::isHexString(const QString& str, char lastAllowedLetter, 0221 const QString& additionalChars) 0222 { 0223 if (str.isEmpty()) { 0224 return false; 0225 } 0226 const char lowerLastAllowedLetter = std::tolower(lastAllowedLetter); 0227 for (int i = 0; i < str.length(); ++i) { 0228 if (char c = str[i].toLatin1(); 0229 !((c >= '0' && c <= '9') || (c >= 'A' && c <= lastAllowedLetter) || 0230 (c >= 'a' && c <= lowerLastAllowedLetter) || 0231 additionalChars.indexOf(QLatin1Char(c)) != -1)) { 0232 return false; 0233 } 0234 } 0235 return true; 0236 }