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 }