File indexing completed on 2024-05-19 04:56:11
0001 /** 0002 * \file frame.cpp 0003 * Generalized frame. 0004 * 0005 * \b Project: Kid3 0006 * \author Urs Fleisch 0007 * \date 25 Aug 2007 0008 * 0009 * Copyright (C) 2007-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 "frame.h" 0028 #include <QVector> 0029 #include <QMap> 0030 #include <QStringList> 0031 #include <QRegularExpression> 0032 #include <QCoreApplication> 0033 #include <QFile> 0034 #include <QTextStream> 0035 #if QT_VERSION >= 0x060000 0036 #include <QStringConverter> 0037 #else 0038 #include <QTextCodec> 0039 #endif 0040 #include "pictureframe.h" 0041 0042 namespace { 0043 0044 const char* const fieldIdNames[] = { 0045 "Unknown", 0046 QT_TRANSLATE_NOOP("@default", "Text Encoding"), 0047 QT_TRANSLATE_NOOP("@default", "Text"), 0048 QT_TRANSLATE_NOOP("@default", "URL"), 0049 QT_TRANSLATE_NOOP("@default", "Data"), 0050 QT_TRANSLATE_NOOP("@default", "Description"), 0051 QT_TRANSLATE_NOOP("@default", "Owner"), 0052 QT_TRANSLATE_NOOP("@default", "Email"), 0053 QT_TRANSLATE_NOOP("@default", "Rating"), 0054 QT_TRANSLATE_NOOP("@default", "Filename"), 0055 QT_TRANSLATE_NOOP("@default", "Language"), 0056 QT_TRANSLATE_NOOP("@default", "Picture Type"), 0057 QT_TRANSLATE_NOOP("@default", "Image format"), 0058 QT_TRANSLATE_NOOP("@default", "Mimetype"), 0059 QT_TRANSLATE_NOOP("@default", "Counter"), 0060 QT_TRANSLATE_NOOP("@default", "Identifier"), 0061 QT_TRANSLATE_NOOP("@default", "Volume Adjustment"), 0062 QT_TRANSLATE_NOOP("@default", "Number of Bits"), 0063 QT_TRANSLATE_NOOP("@default", "Volume Change Right"), 0064 QT_TRANSLATE_NOOP("@default", "Volume Change Left"), 0065 QT_TRANSLATE_NOOP("@default", "Peak Volume Right"), 0066 QT_TRANSLATE_NOOP("@default", "Peak Volume Left"), 0067 QT_TRANSLATE_NOOP("@default", "Timestamp Format"), 0068 QT_TRANSLATE_NOOP("@default", "Content Type"), 0069 0070 QT_TRANSLATE_NOOP("@default", "Price"), 0071 QT_TRANSLATE_NOOP("@default", "Date"), 0072 QT_TRANSLATE_NOOP("@default", "Seller"), 0073 nullptr 0074 }; 0075 0076 const char* const textEncodingNames[] = { 0077 QT_TRANSLATE_NOOP("@default", "ISO-8859-1"), 0078 QT_TRANSLATE_NOOP("@default", "UTF16"), 0079 QT_TRANSLATE_NOOP("@default", "UTF16BE"), 0080 QT_TRANSLATE_NOOP("@default", "UTF8"), 0081 nullptr 0082 }; 0083 0084 const char* const timestampFormatNames[] = { 0085 QT_TRANSLATE_NOOP("@default", "Other"), 0086 QT_TRANSLATE_NOOP("@default", "MPEG frames as unit"), 0087 QT_TRANSLATE_NOOP("@default", "Milliseconds as unit"), 0088 nullptr 0089 }; 0090 0091 const char* const contentTypeNames[] = { 0092 QT_TRANSLATE_NOOP("@default", "Other"), 0093 QT_TRANSLATE_NOOP("@default", "Lyrics"), 0094 QT_TRANSLATE_NOOP("@default", "Text transcription"), 0095 QT_TRANSLATE_NOOP("@default", "Movement/part name"), 0096 QT_TRANSLATE_NOOP("@default", "Events"), 0097 QT_TRANSLATE_NOOP("@default", "Chord"), 0098 QT_TRANSLATE_NOOP("@default", "Trivia/pop up"), 0099 nullptr 0100 }; 0101 0102 // Custom frame names. 0103 QVector<QByteArray> customFrameNames(Frame::NUM_CUSTOM_FRAME_NAMES); 0104 0105 // Map of custom frame names to frame type. 0106 QMap<QByteArray, int> customFrameNameMap; 0107 0108 /** 0109 * Get name of frame from type. 0110 * 0111 * @param type type 0112 * 0113 * @return name. 0114 */ 0115 const char* getNameFromType(Frame::Type type) 0116 { 0117 static const char* const names[] = { 0118 QT_TRANSLATE_NOOP("@default", "Title"), // FT_Title, 0119 QT_TRANSLATE_NOOP("@default", "Artist"), // FT_Artist, 0120 QT_TRANSLATE_NOOP("@default", "Album"), // FT_Album, 0121 QT_TRANSLATE_NOOP("@default", "Comment"), // FT_Comment, 0122 QT_TRANSLATE_NOOP("@default", "Date"), // FT_Date, 0123 QT_TRANSLATE_NOOP("@default", "Track Number"), // FT_Track, 0124 QT_TRANSLATE_NOOP("@default", "Genre"), // FT_Genre, 0125 // FT_LastV1Frame = FT_Track, 0126 QT_TRANSLATE_NOOP("@default", "Album Artist"), // FT_AlbumArtist 0127 QT_TRANSLATE_NOOP("@default", "Arranger"), // FT_Arranger, 0128 QT_TRANSLATE_NOOP("@default", "Author"), // FT_Author, 0129 QT_TRANSLATE_NOOP("@default", "BPM"), // FT_Bpm, 0130 QT_TRANSLATE_NOOP("@default", "Catalog Number"), // FT_CatalogNumber, 0131 QT_TRANSLATE_NOOP("@default", "Compilation"), // FT_Compilation, 0132 QT_TRANSLATE_NOOP("@default", "Composer"), // FT_Composer, 0133 QT_TRANSLATE_NOOP("@default", "Conductor"), // FT_Conductor, 0134 QT_TRANSLATE_NOOP("@default", "Copyright"), // FT_Copyright, 0135 QT_TRANSLATE_NOOP("@default", "Disc Number"), // FT_Disc, 0136 QT_TRANSLATE_NOOP("@default", "Encoded-by"), // FT_EncodedBy, 0137 QT_TRANSLATE_NOOP("@default", "Encoder Settings"), // FT_EncoderSettings, 0138 QT_TRANSLATE_NOOP("@default", "Encoding Time"), // FT_EncodingTime, 0139 QT_TRANSLATE_NOOP("@default", "Grouping"), // FT_Grouping, 0140 QT_TRANSLATE_NOOP("@default", "Initial Key"), // FT_InitialKey, 0141 QT_TRANSLATE_NOOP("@default", "ISRC"), // FT_Isrc, 0142 QT_TRANSLATE_NOOP("@default", "Language"), // FT_Language, 0143 QT_TRANSLATE_NOOP("@default", "Lyricist"), // FT_Lyricist, 0144 QT_TRANSLATE_NOOP("@default", "Lyrics"), // FT_Lyrics, 0145 QT_TRANSLATE_NOOP("@default", "Media"), // FT_Media, 0146 QT_TRANSLATE_NOOP("@default", "Mood"), // FT_Mood, 0147 QT_TRANSLATE_NOOP("@default", "Original Album"), // FT_OriginalAlbum, 0148 QT_TRANSLATE_NOOP("@default", "Original Artist"), // FT_OriginalArtist, 0149 QT_TRANSLATE_NOOP("@default", "Original Date"), // FT_OriginalDate, 0150 QT_TRANSLATE_NOOP("@default", "Description"), // FT_Description, 0151 QT_TRANSLATE_NOOP("@default", "Performer"), // FT_Performer, 0152 QT_TRANSLATE_NOOP("@default", "Picture"), // FT_Picture, 0153 QT_TRANSLATE_NOOP("@default", "Publisher"), // FT_Publisher, 0154 QT_TRANSLATE_NOOP("@default", "Release Country"), // FT_ReleaseCountry, 0155 QT_TRANSLATE_NOOP("@default", "Remixer"), // FT_Remixer, 0156 QT_TRANSLATE_NOOP("@default", "Sort Album"), // FT_SortAlbum, 0157 QT_TRANSLATE_NOOP("@default", "Sort Album Artist"), // FT_SortAlbumArtist, 0158 QT_TRANSLATE_NOOP("@default", "Sort Artist"), // FT_SortArtist, 0159 QT_TRANSLATE_NOOP("@default", "Sort Composer"), // FT_SortComposer, 0160 QT_TRANSLATE_NOOP("@default", "Sort Name"), // FT_SortName, 0161 QT_TRANSLATE_NOOP("@default", "Subtitle"), // FT_Subtitle, 0162 QT_TRANSLATE_NOOP("@default", "Website"), // FT_Website, 0163 QT_TRANSLATE_NOOP("@default", "WWW Audio File"), // FT_WWWAudioFile, 0164 QT_TRANSLATE_NOOP("@default", "WWW Audio Source"), // FT_WWWAudioSource, 0165 QT_TRANSLATE_NOOP("@default", "Release Date"), // FT_ReleaseDate, 0166 QT_TRANSLATE_NOOP("@default", "Rating"), // FT_Rating, 0167 QT_TRANSLATE_NOOP("@default", "Work") // FT_Work, 0168 // FT_Custom1 0169 }; 0170 Q_STATIC_ASSERT(std::size(names) == Frame::FT_Custom1); 0171 if (Frame::isCustomFrameType(type)) { 0172 return Frame::getNameForCustomFrame(type).constData(); 0173 } 0174 return type < Frame::FT_Custom1 ? names[type] : "Unknown"; 0175 } 0176 0177 /** 0178 * Get map of non unified frame names to display names. 0179 * @return mapping of frame names to display names. 0180 */ 0181 QMap<QByteArray, QByteArray> getDisplayNamesOfIds() 0182 { 0183 static const struct StrOfId { 0184 const char* id; 0185 const char* str; 0186 } strOfId[] = { 0187 { "AENC", QT_TRANSLATE_NOOP("@default", "Audio Encryption") }, 0188 { "ASPI", QT_TRANSLATE_NOOP("@default", "Audio Seek Point") }, 0189 { "CHAP", QT_TRANSLATE_NOOP("@default", "Chapter") }, 0190 { "COMR", QT_TRANSLATE_NOOP("@default", "Commercial") }, 0191 { "CTOC", QT_TRANSLATE_NOOP("@default", "Table of Contents") }, 0192 { "ENCR", QT_TRANSLATE_NOOP("@default", "Encryption Method") }, 0193 { "EQU2", QT_TRANSLATE_NOOP("@default", "Equalization") }, 0194 { "EQUA", QT_TRANSLATE_NOOP("@default", "Equalization") }, 0195 { "ETCO", QT_TRANSLATE_NOOP("@default", "Event Timing Codes") }, 0196 { "GEOB", QT_TRANSLATE_NOOP("@default", "General Object") }, 0197 { "GRID", QT_TRANSLATE_NOOP("@default", "Group Identification") }, 0198 { "GRP1", QT_TRANSLATE_NOOP("@default", "Grouping") }, 0199 { "LINK", QT_TRANSLATE_NOOP("@default", "Linked Information") }, 0200 { "MCDI", QT_TRANSLATE_NOOP("@default", "Music CD Identifier") }, 0201 { "MLLT", QT_TRANSLATE_NOOP("@default", "MPEG Lookup Table") }, 0202 { "MVIN", QT_TRANSLATE_NOOP("@default", "Movement Number") }, 0203 { "MVNM", QT_TRANSLATE_NOOP("@default", "Movement Name") }, 0204 { "OWNE", QT_TRANSLATE_NOOP("@default", "Ownership") }, 0205 { "PCNT", QT_TRANSLATE_NOOP("@default", "Play Counter") }, 0206 { "PCST", QT_TRANSLATE_NOOP("@default", "Podcast") }, 0207 { "POPM", QT_TRANSLATE_NOOP("@default", "Popularimeter") }, 0208 { "POSS", QT_TRANSLATE_NOOP("@default", "Position Synchronisation") }, 0209 { "PRIV", QT_TRANSLATE_NOOP("@default", "Private") }, 0210 { "RBUF", QT_TRANSLATE_NOOP("@default", "Recommended Buffer Size") }, 0211 { "RVA2", QT_TRANSLATE_NOOP("@default", "Volume Adjustment") }, 0212 { "RVAD", QT_TRANSLATE_NOOP("@default", "Volume Adjustment") }, 0213 { "RVRB", QT_TRANSLATE_NOOP("@default", "Reverb") }, 0214 { "SEEK", QT_TRANSLATE_NOOP("@default", "Seek") }, 0215 { "SIGN", QT_TRANSLATE_NOOP("@default", "Signature") }, 0216 { "SYLT", QT_TRANSLATE_NOOP("@default", "Synchronized Lyrics") }, 0217 { "SYTC", QT_TRANSLATE_NOOP("@default", "Synchronized Tempo Codes") }, 0218 { "TCAT", QT_TRANSLATE_NOOP("@default", "Podcast Category") }, 0219 { "TDAT", QT_TRANSLATE_NOOP("@default", "Date") }, 0220 { "TDEN", QT_TRANSLATE_NOOP("@default", "Encoding Time") }, 0221 { "TDES", QT_TRANSLATE_NOOP("@default", "Podcast Description") }, 0222 { "TDLY", QT_TRANSLATE_NOOP("@default", "Playlist Delay") }, 0223 { "TDOR", QT_TRANSLATE_NOOP("@default", "Original Release Time") }, 0224 { "TDRC", QT_TRANSLATE_NOOP("@default", "Recording Time") }, 0225 { "TDRL", QT_TRANSLATE_NOOP("@default", "Release Time") }, 0226 { "TDTG", QT_TRANSLATE_NOOP("@default", "Tagging Time") }, 0227 { "TFLT", QT_TRANSLATE_NOOP("@default", "File Type") }, 0228 { "TGID", QT_TRANSLATE_NOOP("@default", "Podcast Identifier") }, 0229 { "TIME", QT_TRANSLATE_NOOP("@default", "Time") }, 0230 { "TKWD", QT_TRANSLATE_NOOP("@default", "Podcast Keywords") }, 0231 { "TLEN", QT_TRANSLATE_NOOP("@default", "Length") }, 0232 { "TOFN", QT_TRANSLATE_NOOP("@default", "Original Filename") }, 0233 { "TOWN", QT_TRANSLATE_NOOP("@default", "File Owner") }, 0234 { "TPRO", QT_TRANSLATE_NOOP("@default", "Produced Notice") }, 0235 { "TRDA", QT_TRANSLATE_NOOP("@default", "Recording Date") }, 0236 { "TRSN", QT_TRANSLATE_NOOP("@default", "Radio Station Name") }, 0237 { "TRSO", QT_TRANSLATE_NOOP("@default", "Radio Station Owner") }, 0238 { "TSIZ", QT_TRANSLATE_NOOP("@default", "Size") }, 0239 { "TXXX", QT_TRANSLATE_NOOP("@default", "User-defined Text") }, 0240 { "UFID", QT_TRANSLATE_NOOP("@default", "Unique File Identifier") }, 0241 { "USER", QT_TRANSLATE_NOOP("@default", "Terms of Use") }, 0242 { "WCOM", QT_TRANSLATE_NOOP("@default", "Commercial URL") }, 0243 { "WCOP", QT_TRANSLATE_NOOP("@default", "Copyright URL") }, 0244 { "WFED", QT_TRANSLATE_NOOP("@default", "Podcast Feed") }, 0245 { "WORS", QT_TRANSLATE_NOOP("@default", "Official Radio Station") }, 0246 { "WPAY", QT_TRANSLATE_NOOP("@default", "Payment") }, 0247 { "WPUB", QT_TRANSLATE_NOOP("@default", "Official Publisher") }, 0248 { "WXXX", QT_TRANSLATE_NOOP("@default", "User-defined URL") }, 0249 { "BAND", QT_TRANSLATE_NOOP("@default", "Album Artist") }, 0250 { "CONTACT", QT_TRANSLATE_NOOP("@default", "Contact") }, 0251 { "CONTENTGROUP", QT_TRANSLATE_NOOP("@default", "Grouping") }, 0252 { "DESCRIPTION", QT_TRANSLATE_NOOP("@default", "Description") }, 0253 { "DISCTOTAL", QT_TRANSLATE_NOOP("@default", "Total Discs") }, 0254 { "ENCODER", QT_TRANSLATE_NOOP("@default", "Encoder") }, 0255 { "ENCODER_OPTIONS", QT_TRANSLATE_NOOP("@default", "Encoder Settings") }, 0256 { "ENCODEDBY", QT_TRANSLATE_NOOP("@default", "Encoded-by") }, 0257 { "ENCODING", QT_TRANSLATE_NOOP("@default", "Encoding") }, 0258 { "ENGINEER", QT_TRANSLATE_NOOP("@default", "Engineer") }, 0259 { "ENSEMBLE", QT_TRANSLATE_NOOP("@default", "Ensemble") }, 0260 { "GUESTARTIST", QT_TRANSLATE_NOOP("@default", "Guest Artist") }, 0261 { "IsVBR", QT_TRANSLATE_NOOP("@default", "VBR") }, 0262 { "iTunPGAP", QT_TRANSLATE_NOOP("@default", "Gapless Playback") }, 0263 { "LABEL", QT_TRANSLATE_NOOP("@default", "Label") }, 0264 { "LABELNO", QT_TRANSLATE_NOOP("@default", "Label Number") }, 0265 { "LICENSE", QT_TRANSLATE_NOOP("@default", "License") }, 0266 { "LOCATION", QT_TRANSLATE_NOOP("@default", "Location") }, 0267 { "OPUS", QT_TRANSLATE_NOOP("@default", "Opus") }, 0268 { "ORIGARTIST", QT_TRANSLATE_NOOP("@default", "Original Artist") }, 0269 { "ORGANIZATION", QT_TRANSLATE_NOOP("@default", "Organization") }, 0270 { "PARTNUMBER", QT_TRANSLATE_NOOP("@default", "Part Number") }, 0271 { "PRODUCER", QT_TRANSLATE_NOOP("@default", "Producer") }, 0272 { "PRODUCTNUMBER", QT_TRANSLATE_NOOP("@default", "Product Number") }, 0273 { "RECORDINGDATE", QT_TRANSLATE_NOOP("@default", "Recording Date") }, 0274 { "REMIXEDBY", QT_TRANSLATE_NOOP("@default", "Remixer") }, 0275 { "TOTALDISCS", QT_TRANSLATE_NOOP("@default", "Total Discs") }, 0276 { "TOTALTRACKS", QT_TRANSLATE_NOOP("@default", "Total Tracks") }, 0277 { "TRACKTOTAL", QT_TRANSLATE_NOOP("@default", "Total Tracks") }, 0278 { "UNKNOWN", QT_TRANSLATE_NOOP("@default", "Unknown") }, 0279 { "Unknown", QT_TRANSLATE_NOOP("@default", "Unknown") }, 0280 { "VERSION", QT_TRANSLATE_NOOP("@default", "Version") }, 0281 { "VOLUME", QT_TRANSLATE_NOOP("@default", "Volume") }, 0282 { "WWW", QT_TRANSLATE_NOOP("@default", "User-defined URL") }, 0283 { "WM/AlbumArtistSortOrder", QT_TRANSLATE_NOOP("@default", "Sort Album Artist") }, 0284 { "WM/Comments", QT_TRANSLATE_NOOP("@default", "Comment") }, 0285 { "WM/MCDI", QT_TRANSLATE_NOOP("@default", "MCDI") }, 0286 { "WM/Mood", QT_TRANSLATE_NOOP("@default", "Mood") }, 0287 { "WM/OriginalFilename", QT_TRANSLATE_NOOP("@default", "Original Filename") }, 0288 { "WM/OriginalLyricist", QT_TRANSLATE_NOOP("@default", "Original Lyricist") }, 0289 { "WM/PromotionURL", QT_TRANSLATE_NOOP("@default", "Commercial URL") }, 0290 { "WM/SharedUserRating", QT_TRANSLATE_NOOP("@default", "User Rating") }, 0291 { "WM/UserWebURL", QT_TRANSLATE_NOOP("@default", "User-defined URL") }, 0292 { "akID", QT_TRANSLATE_NOOP("@default", "Account Type") }, 0293 { "apID", QT_TRANSLATE_NOOP("@default", "Purchase Account") }, 0294 { "atID", QT_TRANSLATE_NOOP("@default", "Artist ID") }, 0295 { "catg", QT_TRANSLATE_NOOP("@default", "Category") }, 0296 { "cnID", QT_TRANSLATE_NOOP("@default", "Catalog ID") }, 0297 { "cond", QT_TRANSLATE_NOOP("@default", "Conductor") }, 0298 { "desc", QT_TRANSLATE_NOOP("@default", "Description") }, 0299 { "geID", QT_TRANSLATE_NOOP("@default", "Genre ID") }, 0300 { "hdvd", QT_TRANSLATE_NOOP("@default", "HD Video") }, 0301 { "keyw", QT_TRANSLATE_NOOP("@default", "Keyword") }, 0302 { "ldes", QT_TRANSLATE_NOOP("@default", "Long Description") }, 0303 { "pcst", QT_TRANSLATE_NOOP("@default", "Podcast") }, 0304 { "pgap", QT_TRANSLATE_NOOP("@default", "Gapless Playback") }, 0305 { "plID", QT_TRANSLATE_NOOP("@default", "Album ID") }, 0306 { "purd", QT_TRANSLATE_NOOP("@default", "Purchase Date") }, 0307 { "rtng", QT_TRANSLATE_NOOP("@default", "Rating/Advisory") }, 0308 { "sfID", QT_TRANSLATE_NOOP("@default", "Country Code") }, 0309 { "sosn", QT_TRANSLATE_NOOP("@default", "Sort Show") }, 0310 { "stik", QT_TRANSLATE_NOOP("@default", "Media Type") }, 0311 { "tven", QT_TRANSLATE_NOOP("@default", "TV Episode") }, 0312 { "tves", QT_TRANSLATE_NOOP("@default", "TV Episode Number") }, 0313 { "tvnn", QT_TRANSLATE_NOOP("@default", "TV Network Name") }, 0314 { "tvsh", QT_TRANSLATE_NOOP("@default", "TV Show Name") }, 0315 { "tvsn", QT_TRANSLATE_NOOP("@default", "TV Season") }, 0316 { "year", QT_TRANSLATE_NOOP("@default", "Year") }, 0317 { "\251mvn", QT_TRANSLATE_NOOP("@default", "Movement Name") }, 0318 { "\251mvi", QT_TRANSLATE_NOOP("@default", "Movement Number") }, 0319 { "\251mvc", QT_TRANSLATE_NOOP("@default", "Movement Count") }, 0320 { "shwm", QT_TRANSLATE_NOOP("@default", "Show Work & Movement") }, 0321 { "ownr", QT_TRANSLATE_NOOP("@default", "Owner") }, 0322 { "purl", QT_TRANSLATE_NOOP("@default", "Podcast URL") }, 0323 { "egid", QT_TRANSLATE_NOOP("@default", "Podcast GUID") }, 0324 { "cmID", QT_TRANSLATE_NOOP("@default", "Composer ID") }, 0325 { "xid ", QT_TRANSLATE_NOOP("@default", "XID") }, 0326 { "Chapters", QT_TRANSLATE_NOOP("@default", "Chapters") }, 0327 { "IARL", QT_TRANSLATE_NOOP("@default", "Archival Location") }, 0328 { "ICMS", QT_TRANSLATE_NOOP("@default", "Commissioned") }, 0329 { "ICRP", QT_TRANSLATE_NOOP("@default", "Cropped") }, 0330 { "IDIM", QT_TRANSLATE_NOOP("@default", "Dimensions") }, 0331 { "IDPI", QT_TRANSLATE_NOOP("@default", "Dots Per Inch") }, 0332 { "IKEY", QT_TRANSLATE_NOOP("@default", "Keywords") }, 0333 { "ILGT", QT_TRANSLATE_NOOP("@default", "Lightness") }, 0334 { "IPLT", QT_TRANSLATE_NOOP("@default", "Number of Colors") }, 0335 { "ISBJ", QT_TRANSLATE_NOOP("@default", "Subject") }, 0336 { "ISHP", QT_TRANSLATE_NOOP("@default", "Sharpness") }, 0337 { "ISRF", QT_TRANSLATE_NOOP("@default", "Source Form") } 0338 }; 0339 static QMap<QByteArray, QByteArray> idStrMap; 0340 if (idStrMap.isEmpty()) { 0341 // first time initialization 0342 for (const auto& [id, str] : strOfId) { 0343 idStrMap.insert(id, str); 0344 } 0345 } 0346 return idStrMap; 0347 } 0348 0349 /** 0350 * Get a reduced field list without fields which are only supported by a 0351 * specific tag format. 0352 * @param fieldList original field list 0353 * @return reduced field list. 0354 */ 0355 Frame::FieldList reducedFieldList(const Frame::FieldList& fieldList) 0356 { 0357 Frame::FieldList reduced; 0358 for (const Frame::Field& fld : fieldList) { 0359 if (fld.m_id != Frame::ID_ImageFormat && 0360 fld.m_id != Frame::ID_ImageProperties) { 0361 reduced.append(fld); 0362 } 0363 } 0364 return reduced; 0365 } 0366 0367 } 0368 0369 Frame::ExtendedType::ExtendedType(const QString& name) : 0370 m_type(getTypeFromName(name)), m_name(name) 0371 { 0372 } 0373 0374 Frame::ExtendedType::ExtendedType(Type type) : 0375 m_type(type), m_name(QString::fromLatin1(getNameFromType(type))) 0376 { 0377 } 0378 0379 /** 0380 * Get name of type. 0381 * @return name. 0382 */ 0383 QString Frame::ExtendedType::getName() const 0384 { 0385 return m_type != FT_Other ? QString::fromLatin1(getNameFromType(m_type)) 0386 : m_name; 0387 } 0388 0389 /** 0390 * Get translated name of type. 0391 * @return name. 0392 */ 0393 QString Frame::ExtendedType::getTranslatedName() const 0394 { 0395 return m_type != FT_Other 0396 ? QCoreApplication::translate("@default", getNameFromType(m_type)) 0397 : m_name; 0398 } 0399 0400 0401 /** 0402 * Constructor. 0403 */ 0404 Frame::Frame() 0405 : m_index(-1), m_marked(FrameNotice::None), m_valueChanged(false) 0406 { 0407 } 0408 0409 /** 0410 * Constructor. 0411 */ 0412 Frame::Frame(Type type, const QString& value, 0413 const QString& name, int index) 0414 : m_extendedType(type, name), m_index(index), m_value(value), 0415 m_marked(FrameNotice::None), m_valueChanged(false) 0416 { 0417 } 0418 0419 /** 0420 * Constructor. 0421 * @param type type and internal name 0422 * @param value value 0423 * @param index index inside tag, -1 if unknown 0424 */ 0425 Frame::Frame(const ExtendedType& type, const QString& value, int index) 0426 : m_extendedType(type), m_index(index), m_value(value), 0427 m_marked(FrameNotice::None), m_valueChanged(false) 0428 { 0429 } 0430 0431 /** 0432 * Set the value from a field in the field list. 0433 */ 0434 void Frame::setValueFromFieldList() 0435 { 0436 if (!getFieldList().empty()) { 0437 for (auto fldIt = getFieldList().constBegin(); 0438 fldIt != getFieldList().constEnd(); 0439 ++fldIt) { 0440 if (int id = fldIt->m_id; 0441 id == ID_Text || 0442 id == ID_Description || 0443 id == ID_Url) { 0444 m_value = fldIt->m_value.toString(); 0445 if (id == ID_Text) { 0446 // highest priority, will not be overwritten 0447 break; 0448 } 0449 } 0450 } 0451 } 0452 } 0453 0454 /** 0455 * Set a field in the field list from the value. 0456 */ 0457 void Frame::setFieldListFromValue() 0458 { 0459 if (!fieldList().empty()) { 0460 auto it = fieldList().end(); // clazy:exclude=detaching-member 0461 for (auto fldIt = fieldList().begin(); fldIt != fieldList().end(); ++fldIt) { // clazy:exclude=detaching-member 0462 if (int id = fldIt->m_id; 0463 id == ID_Text || 0464 id == ID_Description || 0465 id == ID_Url) { 0466 it = fldIt; 0467 if (id == ID_Text) { 0468 // highest priority, will not be overwritten 0469 break; 0470 } 0471 } else if (id == ID_Rating) { 0472 bool ok; 0473 int rating = m_value.toInt(&ok); 0474 if (ok) { 0475 fldIt->m_value = rating; 0476 break; 0477 } 0478 } 0479 } 0480 if (it != fieldList().end()) { 0481 it->m_value = m_value; 0482 } 0483 } 0484 } 0485 0486 /** 0487 * Split a string into a string list using stringListSeparator(). 0488 * @param str string to split at stringListSeparator(), with support of 0489 * '\' as an escape character. 0490 * @return list of strings split at separator character not prefixed with 0491 * escape character. 0492 */ 0493 QStringList Frame::splitStringList(const QString& str) 0494 { 0495 static QRegularExpression separatorRe( 0496 QLatin1String("(?<!\\\\)\\") + stringListSeparator()); 0497 static const QChar sep = stringListSeparator(); 0498 static const QString escSep = QLatin1String("\\") + stringListSeparator(); 0499 QStringList strs = str.split(separatorRe); 0500 for (QString& s : strs) { 0501 s.replace(escSep, sep); 0502 } 0503 return strs; 0504 } 0505 0506 /** 0507 * Join a string list using stringListSeparator(). 0508 * @param strs strings to join, if they contain the separator character, 0509 * it will be escaped with '\'. 0510 * @return escaped strings joined by separator characters. 0511 */ 0512 QString Frame::joinStringList(const QStringList& strs) 0513 { 0514 static const QChar sep = stringListSeparator(); 0515 static const QString escSep = QLatin1String("\\") + stringListSeparator(); 0516 QStringList escapedStrs(strs); 0517 for (QString& str : escapedStrs) { 0518 str.replace(sep, escSep); 0519 } 0520 return escapedStrs.join(sep); 0521 } 0522 0523 /** 0524 * Convert string (e.g. "track/total number of tracks") to number. 0525 * 0526 * @param str string to convert 0527 * @param ok if not 0, true is returned here if conversion is ok 0528 * 0529 * @return number in string ignoring total after slash. 0530 */ 0531 int Frame::numberWithoutTotal(const QString& str, bool* ok) 0532 { 0533 int slashPos = str.indexOf(QLatin1Char('/')); 0534 #if QT_VERSION >= 0x060000 0535 return slashPos == -1 ? str.toInt(ok) : str.left(slashPos).toInt(ok); 0536 #else 0537 return slashPos == -1 ? str.toInt(ok) : str.leftRef(slashPos).toInt(ok); 0538 #endif 0539 } 0540 0541 /** 0542 * Get value as integer. 0543 * @return value. 0544 */ 0545 int Frame::getValueAsNumber() const 0546 { 0547 if (isInactive()) { 0548 return -1; 0549 } 0550 if (isEmpty()) { 0551 return 0; 0552 } 0553 return numberWithoutTotal(m_value); 0554 } 0555 0556 /** 0557 * Set value as integer. 0558 * @param n value as number 0559 */ 0560 void Frame::setValueAsNumber(int n) 0561 { 0562 if (n == -1) { 0563 m_value = QString(); 0564 } else if (n == 0) { 0565 m_value = QLatin1String(""); 0566 } else { 0567 m_value.setNum(n); 0568 } 0569 } 0570 0571 /** 0572 * Check if the value of this frame is fuzzy equal to another frame. 0573 * Other than with strict equality, total values and some fields which are 0574 * not supported in all tag formats are ignored. 0575 * @param other frame to compare 0576 * @return true if more or less equal. 0577 */ 0578 bool Frame::isFuzzyEqual(const Frame& other) const 0579 { 0580 if (getType() == FT_Track || getType() == FT_Disc) { 0581 return getValueAsNumber() == other.getValueAsNumber(); 0582 } 0583 return getValue() == other.getValue() && 0584 (getFieldList().isEmpty() || 0585 other.getFieldList().isEmpty() || 0586 Field::fuzzyCompareFieldLists(getFieldList(), 0587 other.getFieldList())); 0588 } 0589 0590 /** 0591 * Get the value of a field. 0592 * 0593 * @param id field ID 0594 * 0595 * @return field value, invalid if field not found. 0596 */ 0597 QVariant Frame::getFieldValue(FieldId id) const 0598 { 0599 for (auto it = m_fieldList.constBegin(); it != m_fieldList.constEnd(); ++it) { 0600 if (it->m_id == id) { 0601 return it->m_value; 0602 } 0603 } 0604 return QVariant(); 0605 } 0606 0607 /** 0608 * Set value as string and mark it as changed if it is changed. 0609 * This method will avoid setting "different" representations. 0610 * @param value value as string 0611 */ 0612 void Frame::setValueIfChanged(const QString& value) 0613 { 0614 if (value != differentRepresentation()) { 0615 if (QString oldValue(getValue()); 0616 value != oldValue && !(value.isEmpty() && oldValue.isEmpty())) { 0617 setValue(value); 0618 setValueChanged(); 0619 } 0620 } 0621 } 0622 0623 /** 0624 * Read value text from file. 0625 * @param fileName name of data file 0626 * @return true if file read and value set. 0627 */ 0628 bool Frame::setValueFromFile(const QString& fileName) 0629 { 0630 if (!fileName.isEmpty()) { 0631 if (QFile file(fileName); file.open(QIODevice::ReadOnly)) { 0632 QString value; 0633 QByteArray data = file.readAll(); 0634 #if QT_VERSION >= 0x060000 0635 auto toUtf8 = QStringDecoder(QStringConverter::Utf8); 0636 value = toUtf8(data); 0637 if (toUtf8.hasError()) { 0638 auto encoding = QStringConverter::encodingForData(data); 0639 auto decoder = QStringDecoder(encoding.value_or(QStringConverter::Latin1)); 0640 value = decoder(data); 0641 } 0642 #else 0643 QTextCodec::ConverterState state; 0644 if (QTextCodec* codec = QTextCodec::codecForName("UTF-8")) { 0645 value = codec->toUnicode(data.constData(), data.size(), &state); 0646 if (state.invalidChars > 0) { 0647 codec = QTextCodec::codecForUtfText( 0648 data, QTextCodec::codecForName("ISO 8859-1")); 0649 if (codec) { 0650 value = codec->toUnicode(data.constData(), data.size()); 0651 } 0652 } 0653 } 0654 #endif 0655 setValueIfChanged(value); 0656 file.close(); 0657 return true; 0658 } 0659 } 0660 return false; 0661 } 0662 0663 /** 0664 * Save value text to a file. 0665 * @param fileName name of data file to save 0666 * @return true if saved. 0667 */ 0668 bool Frame::writeValueToFile(const QString& fileName) const 0669 { 0670 if (!fileName.isEmpty()) { 0671 if (QFile file(fileName); file.open(QIODevice::WriteOnly)) { 0672 file.write(m_value.toUtf8()); 0673 file.close(); 0674 return true; 0675 } 0676 } 0677 return false; 0678 } 0679 0680 /** 0681 * Check if the fields in another frame are equal. 0682 * 0683 * @param other other frame 0684 * 0685 * @return true if equal. 0686 */ 0687 bool Frame::isEqual(const Frame& other) const 0688 { 0689 if (getType() != other.getType() || getValue() != other.getValue()) 0690 return false; 0691 0692 const FieldList& otherFieldList = other.getFieldList(); 0693 if (m_fieldList.size() != otherFieldList.size()) 0694 return false; 0695 0696 for (auto thisIt = m_fieldList.constBegin(), otherIt = otherFieldList.constBegin(); 0697 thisIt != m_fieldList.constEnd() && otherIt != otherFieldList.constEnd(); 0698 ++thisIt, ++otherIt) { 0699 if (thisIt->m_id != otherIt->m_id || thisIt->m_value != otherIt->m_value) { 0700 return false; 0701 } 0702 } 0703 0704 return true; 0705 } 0706 0707 /** 0708 * Set value of a field. 0709 * 0710 * @param frame frame to set 0711 * @param id field ID 0712 * @param value field value 0713 * 0714 * @return true if field found and set. 0715 */ 0716 bool Frame::setField(Frame& frame, FieldId id, const QVariant& value) 0717 { 0718 for (auto it = frame.fieldList().begin(); it != frame.fieldList().end(); ++it) { // clazy:exclude=detaching-member 0719 if (it->m_id == id) { 0720 it->m_value = value; 0721 if (id == ID_Description) frame.setValue(value.toString()); 0722 return true; 0723 } 0724 } 0725 return false; 0726 } 0727 0728 /** 0729 * Set value of a field. 0730 * 0731 * @param frame frame to set 0732 * @param fieldName name of field, can be English or translated 0733 * @param value field value 0734 * 0735 * @return true if field found and set. 0736 */ 0737 bool Frame::setField(Frame& frame, const QString& fieldName, 0738 const QVariant& value) 0739 { 0740 if (const FieldId id = Field::getFieldId(fieldName); id != ID_NoField) { 0741 #if QT_VERSION >= 0x060000 0742 QMetaType valueType = value.metaType(); 0743 QMetaType fieldType; 0744 #else 0745 QVariant::Type valueType = value.type(); 0746 QVariant::Type fieldType; 0747 #endif 0748 switch (id) { 0749 case ID_TextEnc: 0750 case ID_PictureType: 0751 case ID_Counter: 0752 case ID_VolumeAdj: 0753 case ID_NumBits: 0754 case ID_VolChgRight: 0755 case ID_VolChgLeft: 0756 case ID_PeakVolRight: 0757 case ID_PeakVolLeft: 0758 case ID_TimestampFormat: 0759 case ID_ContentType: 0760 #if QT_VERSION >= 0x060000 0761 fieldType = QMetaType(QMetaType::Int); 0762 #else 0763 fieldType = QVariant::Int; 0764 #endif 0765 break; 0766 case ID_Data: 0767 #if QT_VERSION >= 0x060000 0768 fieldType = QMetaType(QMetaType::QByteArray); 0769 #else 0770 fieldType = QVariant::ByteArray; 0771 #endif 0772 break; 0773 default: 0774 #if QT_VERSION >= 0x060000 0775 fieldType = QMetaType(QMetaType::QString); 0776 #else 0777 fieldType = QVariant::String; 0778 #endif 0779 } 0780 if (valueType != fieldType && value.canConvert(fieldType)) { 0781 if (QVariant converted(value); converted.convert(fieldType)) { 0782 return setField(frame, id, converted); 0783 } 0784 } 0785 return setField(frame, id, value); 0786 } 0787 return false; 0788 } 0789 0790 /** 0791 * Get value of a field. 0792 * 0793 * @param frame frame to get 0794 * @param id field ID 0795 * 0796 * @return field value, invalid if not found. 0797 */ 0798 QVariant Frame::getField(const Frame& frame, FieldId id) 0799 { 0800 QVariant result; 0801 if (!frame.getFieldList().empty()) { 0802 for (auto it = frame.getFieldList().constBegin(); 0803 it != frame.getFieldList().constEnd(); 0804 ++it) { 0805 if (it->m_id == id) { 0806 result = it->m_value; 0807 break; 0808 } 0809 } 0810 } 0811 return result; 0812 } 0813 0814 /** 0815 * Get value of a field. 0816 * 0817 * @param frame frame to get 0818 * @param fieldName name of field, can be English or translated 0819 * 0820 * @return field value, invalid if not found. 0821 */ 0822 QVariant Frame::getField(const Frame& frame, const QString& fieldName) 0823 { 0824 const FieldId id = Field::getFieldId(fieldName); 0825 return id != ID_NoField ? getField(frame, id) : QVariant(); 0826 } 0827 0828 /** 0829 * Get type of frame from English name. 0830 * 0831 * @param name name, spaces and case are ignored 0832 * 0833 * @return type. 0834 */ 0835 Frame::Type Frame::getTypeFromName(const QString& name) 0836 { 0837 static QMap<QString, int> strNumMap; 0838 if (strNumMap.empty()) { 0839 // first time initialization 0840 for (int i = 0; i < Frame::FT_Custom1; ++i) { 0841 auto type = static_cast<Frame::Type>(i); 0842 strNumMap.insert(QString::fromLatin1(getNameFromType(type)) 0843 .remove(QLatin1Char(' ')).toUpper(), type); 0844 } 0845 } 0846 QString ucName(name.toUpper()); 0847 ucName.remove(QLatin1Char(' ')); 0848 if (auto it = strNumMap.constFind(ucName); it != strNumMap.constEnd()) { 0849 return static_cast<Frame::Type>(*it); 0850 } 0851 return getTypeFromCustomFrameName(name.toLatin1()); 0852 } 0853 0854 /** 0855 * Get a translated string for a frame type. 0856 * 0857 * @param type frame type 0858 * 0859 * @return frame type, null string if unknown. 0860 */ 0861 QString Frame::getFrameTypeName(Type type) 0862 { 0863 return QCoreApplication::translate("@default", getNameFromType(type)); 0864 } 0865 0866 /** 0867 * Get a display name for a frame name. 0868 * @param name frame name as returned by getName() 0869 * @return display name, transformed if necessary and translated. 0870 */ 0871 QString Frame::getDisplayName(const QString& name) 0872 { 0873 QMap<QByteArray, QByteArray> idStrMap = getDisplayNamesOfIds(); 0874 if (name.isEmpty()) 0875 return name; 0876 0877 if (Type type = getTypeFromName(name); !Frame::isCustomFrameTypeOrOther(type)) 0878 return QCoreApplication::translate("@default", 0879 name.toLatin1().constData()); 0880 0881 QString nameStr(name); 0882 if (int nlPos = nameStr.indexOf(QLatin1Char('\n')); nlPos > 0) 0883 // probably "TXXX - User defined text information\nDescription" or 0884 // "WXXX - User defined URL link\nDescription" 0885 nameStr = nameStr.mid(nlPos + 1); 0886 0887 QByteArray id; 0888 if (nameStr.mid(4, 3) == QLatin1String(" - ")) { 0889 id = nameStr.left(4).toLatin1(); 0890 } else { 0891 id = nameStr.toLatin1(); 0892 } 0893 0894 if (auto it = idStrMap.constFind(id); it != idStrMap.constEnd()) { 0895 return QCoreApplication::translate("@default", it->constData()); 0896 } 0897 return nameStr; 0898 } 0899 0900 /** 0901 * Get a map with display names as keys and frame names as values. 0902 * @param names frame names as returned by getName() 0903 * @return mapping of display names to frame names. 0904 */ 0905 QMap<QString, QString> Frame::getDisplayNameMap(const QStringList& names) 0906 { 0907 QMap<QString, QString> map; 0908 for (const QString& name : names) { 0909 map.insert(getDisplayName(name), name); 0910 } 0911 return map; 0912 } 0913 0914 /** 0915 * Get the frame name for a translated display name. 0916 * @param name translated display name 0917 * @return English frame name for @a name if found, else @a name. 0918 */ 0919 QString Frame::getNameForTranslatedFrameName(const QString& name) 0920 { 0921 static QMap<QString, QString> nameMap; 0922 if (nameMap.isEmpty()) { 0923 // first time initialization 0924 for (int k = Frame::FT_FirstFrame; k < Frame::FT_Custom1; ++k) { 0925 QString typeName = Frame::ExtendedType(static_cast<Frame::Type>(k), 0926 QLatin1String("")).getName(); 0927 nameMap.insert(QCoreApplication::translate("@default", 0928 typeName.toLatin1().constData()), typeName); 0929 } 0930 QMap<QByteArray, QByteArray> idStrMap = getDisplayNamesOfIds(); 0931 const auto names = idStrMap.values(); 0932 for (const QByteArray& frameName : names) { 0933 nameMap.insert(QCoreApplication::translate("@default", frameName), 0934 QString::fromLatin1(frameName)); 0935 } 0936 } 0937 return nameMap.value(name, name); 0938 } 0939 0940 /** 0941 * Get internal frame ID of non unified frame for a translated display name. 0942 * @param name translated display name 0943 * @return internal frame ID, e.g. "SYLT", "keyw" of non unified frame, 0944 * null if @a name is not the name of a supported non unified frame. 0945 */ 0946 QByteArray Frame::getFrameIdForTranslatedFrameName(const QString& name) 0947 { 0948 static QMap<QString, QByteArray> nameMap; 0949 if (nameMap.isEmpty()) { 0950 // first time initialization 0951 const QMap<QByteArray, QByteArray> idStrMap = getDisplayNamesOfIds(); 0952 for (auto it = idStrMap.constBegin(); it != idStrMap.constEnd(); ++it) { 0953 nameMap.insert(QCoreApplication::translate("@default", it.value()), 0954 it.key()); 0955 } 0956 } 0957 return nameMap.value(name); 0958 } 0959 0960 /** 0961 * Get the internal name for a custom frame. 0962 * @param type custom frame type (FT_Custom1..FT_LastFrame) 0963 * @return custom frame name, empty if not used. 0964 */ 0965 QByteArray Frame::getNameForCustomFrame(Frame::Type type) 0966 { 0967 if (int idx = type - FT_Custom1; idx >= 0 && idx < customFrameNames.size()) { 0968 return customFrameNames.at(idx); 0969 } 0970 return ""; 0971 } 0972 0973 /** 0974 * Get type of frame from custom frame name. 0975 * @param name custom frame name 0976 * @return type, FT_Other if no custom frame with @a name exists. 0977 */ 0978 Frame::Type Frame::getTypeFromCustomFrameName(const QByteArray& name) 0979 { 0980 if (customFrameNameMap.isEmpty()) { 0981 // first time initialization 0982 for (int i = 0; i < customFrameNames.size(); ++i) { 0983 auto type = static_cast<Frame::Type>(FT_Custom1 + i); 0984 if (QByteArray customFrameName = customFrameNames.at(i).toUpper() 0985 .replace(' ', QByteArray()); 0986 !customFrameName.isEmpty()) { 0987 customFrameNameMap.insert(customFrameName, type); 0988 } 0989 } 0990 } 0991 auto ucName = name.toUpper().replace(' ', QByteArray()); 0992 if (auto it = customFrameNameMap.constFind(ucName); 0993 it != customFrameNameMap.constEnd()) { 0994 return static_cast<Frame::Type>(*it); 0995 } 0996 return Frame::FT_Other; 0997 } 0998 0999 /** 1000 * Set the internal names for all custom frames. 1001 * The number of custom frames is limited. The internal vector will be resized 1002 * to fit the fixed number of custom frames. 1003 * 1004 * @param customNames names for the custom frame types (FT_Custom1...), 1005 * leading '!' will be removed from the names 1006 * @return true if custom frame names were changed. 1007 */ 1008 bool Frame::setNamesForCustomFrames(const QStringList& customNames) 1009 { 1010 QVector<QByteArray> newCustomFrameNames(NUM_CUSTOM_FRAME_NAMES); 1011 int i = 0; 1012 for (auto it = customNames.constBegin(); it != customNames.constEnd(); ++it) { 1013 if (i >= NUM_CUSTOM_FRAME_NAMES) { 1014 break; 1015 } 1016 QString name = *it; 1017 if (name.startsWith(QLatin1Char('!'))) { 1018 name.remove(0, 1); 1019 } 1020 if (!name.isEmpty()) { 1021 newCustomFrameNames[i++] = name.toLatin1(); 1022 } 1023 } 1024 if (customFrameNames != newCustomFrameNames) { 1025 customFrameNames.swap(newCustomFrameNames); 1026 // Invalidate mapping used by getTypeFromName() 1027 customFrameNameMap.clear(); 1028 return true; 1029 } 1030 return false; 1031 } 1032 1033 /** 1034 * Get the names for all custom frames. 1035 * The returned list does not contain empty names. 1036 * 1037 * @return names for the custom frame types (FT_Custom1, ...). 1038 */ 1039 QStringList Frame::getNamesForCustomFrames() 1040 { 1041 QStringList names; 1042 for (auto it = customFrameNames.constBegin(); 1043 it != customFrameNames.constEnd(); 1044 ++it) { 1045 if (!it->isEmpty()) { 1046 names.append(QString::fromLatin1(*it)); 1047 } 1048 } 1049 return names; 1050 } 1051 1052 /** 1053 * Get a translated string for a field ID. 1054 * 1055 * @param type field ID type 1056 * 1057 * @return field ID type, null string if unknown. 1058 */ 1059 QString Frame::Field::getFieldIdName(FieldId type) 1060 { 1061 Q_STATIC_ASSERT(std::size(fieldIdNames) == ID_Seller + 2); 1062 if (static_cast<int>(type) >= 0 && 1063 static_cast<int>(type) < static_cast<int>(std::size(fieldIdNames) - 1)) { 1064 return QCoreApplication::translate("@default", fieldIdNames[type]); 1065 } 1066 return QString(); 1067 } 1068 1069 /** 1070 * List of field ID strings, NULL terminated. 1071 */ 1072 const char* const* Frame::Field::getFieldIdNames() 1073 { 1074 return fieldIdNames; 1075 } 1076 1077 /** 1078 * Get field ID from field name. 1079 * @param fieldName name of field, can be English or translated 1080 * @return field ID, ID_NoField if not found. 1081 */ 1082 Frame::FieldId Frame::Field::getFieldId(const QString& fieldName) 1083 { 1084 const char* const* fn; 1085 int id; 1086 // First try to find an exact English match. 1087 for (fn = fieldIdNames, id = 0; *fn != nullptr; ++fn, ++id) { 1088 if (fieldName == QLatin1String(*fn)) { 1089 return static_cast<FieldId>(id); 1090 } 1091 } 1092 // Then try to find a lowercase match ignoring spaces. 1093 QString lcName = fieldName.toLower().remove(QLatin1Char(' ')); 1094 for (fn = fieldIdNames, id = 0; *fn != nullptr; ++fn, ++id) { 1095 if (lcName == QString::fromLatin1(*fn).toLower().remove(QLatin1Char(' '))) { 1096 return static_cast<FieldId>(id); 1097 } 1098 } 1099 // Finally try to find a translated name. 1100 for (fn = fieldIdNames, id = 0; *fn != nullptr; ++fn, ++id) { 1101 if (fieldName == QCoreApplication::translate("@default", *fn)) { 1102 return static_cast<FieldId>(id); 1103 } 1104 } 1105 return ID_NoField; 1106 } 1107 1108 /** 1109 * Get a translated string for a text encoding. 1110 * 1111 * @param type text encoding type 1112 * 1113 * @return text encoding type, null string if unknown. 1114 */ 1115 QString Frame::Field::getTextEncodingName(TextEncoding type) 1116 { 1117 if (static_cast<int>(type) >= 0 && 1118 static_cast<int>(type) < static_cast<int>( 1119 std::size(textEncodingNames) - 1)) { 1120 return QCoreApplication::translate("@default", textEncodingNames[type]); 1121 } 1122 return QString(); 1123 } 1124 1125 /** 1126 * List of text encoding strings, NULL terminated. 1127 */ 1128 const char* const* Frame::Field::getTextEncodingNames() 1129 { 1130 return textEncodingNames; 1131 } 1132 1133 /** 1134 * Get a translated string for a timestamp format. 1135 * 1136 * @param type timestamp format type 1137 * 1138 * @return timestamp format type, null string if unknown. 1139 */ 1140 QString Frame::Field::getTimestampFormatName(int type) 1141 { 1142 if (type >= 0 && 1143 static_cast<unsigned int>(type) < std::size(timestampFormatNames) - 1) { 1144 return QCoreApplication::translate("@default", timestampFormatNames[type]); 1145 } 1146 return QString(); 1147 } 1148 1149 /** 1150 * List of timestamp format strings, NULL terminated. 1151 */ 1152 const char* const* Frame::Field::getTimestampFormatNames() 1153 { 1154 return timestampFormatNames; 1155 } 1156 1157 /** 1158 * Get a translated string for a content type. 1159 * 1160 * @param type content type 1161 * 1162 * @return content type, null string if unknown. 1163 */ 1164 QString Frame::Field::getContentTypeName(int type) 1165 { 1166 if (type >= 0 && 1167 static_cast<unsigned int>(type) < std::size(contentTypeNames) - 1) { 1168 return QCoreApplication::translate("@default", contentTypeNames[type]); 1169 } 1170 return QString(); 1171 } 1172 1173 /** 1174 * List of content type strings, NULL terminated. 1175 */ 1176 const char* const* Frame::Field::getContentTypeNames() 1177 { 1178 return contentTypeNames; 1179 } 1180 1181 /** 1182 * Compare two field lists in a tolerant way. 1183 * This function can be used instead of the standard QList equality 1184 * operator if the field lists can be from different tag formats, which 1185 * may not all support the same field types. 1186 * @param fl1 first field list 1187 * @param fl2 second field list 1188 * @return true if they are similar enough. 1189 */ 1190 bool Frame::Field::fuzzyCompareFieldLists(const QList<Field>& fl1, 1191 const QList<Field>& fl2) 1192 { 1193 return reducedFieldList(fl1) == reducedFieldList(fl2); 1194 } 1195 1196 /** 1197 * Get list of available tag versions with translated description. 1198 * @return tag version/description pairs. 1199 */ 1200 const QList<QPair<Frame::TagVersion, QString> > Frame::availableTagVersions() 1201 { 1202 QList<QPair<TagVersion, QString> > result; 1203 FOR_ALL_TAGS(tagNr) { 1204 const char* const tagStr = QT_TRANSLATE_NOOP("@default", "Tag %1"); 1205 result << qMakePair(Frame::tagVersionCast(1 << tagNr), 1206 QCoreApplication::translate("@default", tagStr) 1207 .arg(Frame::tagNumberToString(tagNr))); 1208 } 1209 const char* const tag12Str = QT_TRANSLATE_NOOP("@default", "Tag 1 and Tag 2"); 1210 result << qMakePair(TagV2V1, QCoreApplication::translate("@default", 1211 tag12Str)); 1212 if constexpr (TagVAll != TagV2V1) { 1213 const char* const allTagsStr = QT_TRANSLATE_NOOP("@default", "All Tags"); 1214 result << qMakePair(TagVAll, QCoreApplication::translate("@default", 1215 allTagsStr)); 1216 } 1217 return result; 1218 } 1219 1220 /** 1221 * Get string representation for tag number. 1222 * @param tagNr tag number 1223 * @return "1" for Tag_1, "2" for Tag_2, ..., null if invalid. 1224 */ 1225 QString Frame::tagNumberToString(TagNumber tagNr) 1226 { 1227 return tagNr < Tag_NumValues ? QString::number(tagNr + 1) : QString(); 1228 } 1229 1230 /** 1231 * Get tag number from string representation. 1232 * @param str string representation 1233 * @return Tag_1 for "1", Tag_2 for "2", ..., Tag_NumValues if invalid. 1234 */ 1235 Frame::TagNumber Frame::tagNumberFromString(const QString& str) 1236 { 1237 bool ok; 1238 int nr = str.toInt(&ok); 1239 return ok && --nr >= Frame::Tag_1 && nr < Frame::Tag_NumValues 1240 ? static_cast<Frame::TagNumber>(nr) : Tag_NumValues; 1241 } 1242 1243 #ifndef QT_NO_DEBUG 1244 1245 namespace { 1246 1247 /** 1248 * Get string representation of variant. 1249 * @param val variant value 1250 * @return string representation. 1251 */ 1252 QString variantToString(const QVariant& val) 1253 { 1254 #if QT_VERSION >= 0x060000 1255 if (val.typeId() == QMetaType::QByteArray) { 1256 #else 1257 if (val.type() == QVariant::ByteArray) { 1258 #endif 1259 return QString(QLatin1String("ByteArray of %1 bytes")) 1260 .arg(val.toByteArray().size()); 1261 } 1262 return val.toString(); 1263 } 1264 1265 } 1266 1267 /** 1268 * Dump contents of frame to debug console. 1269 */ 1270 void Frame::dump() const 1271 { 1272 qDebug("Frame: name=%s, value=%s, type=%s, index=%d, valueChanged=%u, marked=%s", 1273 getInternalName().toLatin1().data(), m_value.toLatin1().data(), 1274 getNameFromType(getType()), m_index, 1275 m_valueChanged, qPrintable(m_marked.getDescription())); 1276 qDebug(" fields="); 1277 for (auto it = m_fieldList.constBegin(); it != m_fieldList.constEnd(); ++it) { 1278 qDebug(" Field: id=%s, value=%s", 1279 qPrintable( 1280 Field::getFieldIdName(static_cast<FieldId>(it->m_id))), 1281 variantToString(it->m_value).toLatin1().data()); 1282 } 1283 } 1284 #endif 1285 1286 1287 /** 1288 * Bit mask containing the bits of all frame types which shall be used as 1289 * quick access frames. 1290 * This mask has to be handled like FrameFilter::m_enabledFrames. 1291 */ 1292 quint64 FrameCollection::s_quickAccessFrames = 1293 FrameCollection::DEFAULT_QUICK_ACCESS_FRAMES; 1294 1295 /** 1296 * Set values which are different inactive. 1297 * 1298 * @param others frames to compare, will be modified 1299 * @param differentValues optional storage for the different values 1300 */ 1301 void FrameCollection::filterDifferent( 1302 FrameCollection& others, 1303 QHash<Frame::ExtendedType, QSet<QString>>* differentValues) 1304 { 1305 constexpr int ALREADY_HANDLED_INDEX = INT_MIN; 1306 QByteArray frameData, othersData; 1307 auto it = begin(); 1308 while (it != end()) { 1309 auto& frame = const_cast<Frame&>(*it); 1310 // This frame list is not tied to a specific file, so the 1311 // index is not valid. 1312 frame.setIndex(-1); 1313 if (auto othersIt = others.find(frame); othersIt == others.end()) { 1314 frame.setDifferent(); 1315 ++it; 1316 } else { 1317 while (it != end() && othersIt != others.end() && 1318 !(frame < *it) && !(frame < *othersIt)) { 1319 if ((it->getType() != Frame::FT_Picture && 1320 it->getValue() != othersIt->getValue()) || 1321 (it->getType() == Frame::FT_Picture && 1322 !(PictureFrame::getData(*it, frameData) && 1323 PictureFrame::getData(*othersIt, othersData) && 1324 frameData == othersData))) { 1325 if (differentValues && it->getType() != Frame::FT_Picture && 1326 it->getType() != Frame::FT_Genre) { 1327 auto& valueSet = (*differentValues)[it->getExtendedType()]; 1328 if (it->getValue() != Frame::differentRepresentation()) { 1329 valueSet.insert(it->getValue()); 1330 } 1331 valueSet.insert(othersIt->getValue()); 1332 } 1333 const_cast<Frame&>(*it).setDifferent(); 1334 } 1335 // Mark as already handled. 1336 const_cast<Frame&>(*othersIt).setIndex(ALREADY_HANDLED_INDEX); 1337 ++it; 1338 ++othersIt; 1339 } 1340 } 1341 } 1342 1343 // Insert frames which are in others but not in this (not marked as already 1344 // handled by index ALREADY_HANDLED_INDEX) as different frames. 1345 for (auto othersIt = others.begin(); 1346 othersIt != others.end(); 1347 ++othersIt) { 1348 if (othersIt->getIndex() != ALREADY_HANDLED_INDEX) { 1349 auto& frame = const_cast<Frame&>(*othersIt); 1350 frame.setIndex(-1); 1351 frame.setDifferent(); 1352 insert(frame); 1353 } 1354 } 1355 } 1356 1357 /** 1358 * Add standard frames which are missing. 1359 */ 1360 void FrameCollection::addMissingStandardFrames() 1361 { 1362 quint64 mask; 1363 int i; 1364 for (i = Frame::FT_FirstFrame, mask = 1ULL; 1365 i <= Frame::FT_LastFrame; 1366 ++i, mask <<= 1) { 1367 if (s_quickAccessFrames & mask) { 1368 Frame frame(static_cast<Frame::Type>(i), QString(), QString(), -1); 1369 if (auto it = find(frame); it == end()) { 1370 insert(frame); 1371 } 1372 } 1373 } 1374 } 1375 1376 /** 1377 * Copy enabled frames. 1378 * 1379 * @param flt filter with enabled frames 1380 * 1381 * @return copy with enabled frames. 1382 */ 1383 FrameCollection FrameCollection::copyEnabledFrames(const FrameFilter& flt) const 1384 { 1385 FrameCollection frames; 1386 for (auto it = cbegin(); it != cend(); ++it) { 1387 if (flt.isEnabled(it->getType(), it->getName())) { 1388 Frame frame = *it; 1389 frame.setIndex(-1); 1390 frames.insert(frame); 1391 } 1392 } 1393 return frames; 1394 } 1395 1396 /** 1397 * Remove all frames which are not enabled from the collection. 1398 * 1399 * @param flt filter with enabled frames 1400 */ 1401 void FrameCollection::removeDisabledFrames(const FrameFilter& flt) 1402 { 1403 for (auto it = begin(); it != end();) { 1404 if (!flt.isEnabled(it->getType(), it->getName())) { 1405 erase(it++); 1406 } else { 1407 ++it; 1408 } 1409 } 1410 } 1411 1412 /** 1413 * Set the index of all frames to -1. 1414 */ 1415 void FrameCollection::setIndexesInvalid() 1416 { 1417 for (auto it = begin(); it != end(); ++it) { 1418 auto& frame = const_cast<Frame&>(*it); 1419 frame.setIndex(-1); 1420 } 1421 } 1422 1423 /** 1424 * Copy frames which are empty or inactive from other frames. 1425 * This can be used to merge two frame collections. 1426 * 1427 * @param frames other frames 1428 */ 1429 void FrameCollection::merge(const FrameCollection& frames) 1430 { 1431 for (auto otherIt = frames.cbegin(); otherIt != frames.cend(); ++otherIt) { 1432 if (auto it = find(*otherIt); it != end()) { 1433 QString value(otherIt->getValue()); 1434 if (auto& frameFound = const_cast<Frame&>(*it); 1435 frameFound.getValue().isEmpty() && !value.isEmpty()) { 1436 frameFound.setValueIfChanged(value); 1437 } 1438 } else { 1439 Frame frame(*otherIt); 1440 frame.setIndex(-1); 1441 frame.setValueChanged(true); 1442 insert(frame); 1443 } 1444 } 1445 } 1446 1447 /** 1448 * Check if the standard tags are empty or inactive. 1449 * 1450 * @return true if empty or inactive. 1451 */ 1452 bool FrameCollection::isEmptyOrInactive() const 1453 { 1454 return 1455 getTitle().isEmpty() && 1456 getArtist().isEmpty() && 1457 getAlbum().isEmpty() && 1458 getComment().isEmpty() && 1459 getYear() <= 0 && 1460 getTrack() <= 0 && 1461 getGenre().isEmpty(); 1462 } 1463 1464 /** 1465 * Search for a frame only by name. 1466 * 1467 * @param name the name of the frame to find, a case-insensitive search for 1468 * the first name starting with this string is performed 1469 * 1470 * @return iterator or end() if not found. 1471 */ 1472 FrameCollection::const_iterator FrameCollection::searchByName( 1473 const QString& name) const 1474 { 1475 if (name.isEmpty()) 1476 return cend(); 1477 1478 const_iterator it; 1479 QString ucName = name.toUpper().remove(QLatin1Char('/')); 1480 int len = ucName.length(); 1481 for (it = cbegin(); it != cend(); ++it) { 1482 const QStringList names{it->getName(), it->getInternalName()}; 1483 for (const QString& frameName : names) { 1484 QString ucFrameName(frameName.toUpper().remove(QLatin1Char('/'))); 1485 #if QT_VERSION >= 0x060000 1486 if (ucName == ucFrameName.left(len)) 1487 #else 1488 // Do not return ASF "Rating Information" when searching for "Rating". 1489 if (ucName == ucFrameName.leftRef(len) && 1490 !(ucName == QLatin1String("RATING") && 1491 ucFrameName == QLatin1String("RATING INFORMATION"))) 1492 #endif 1493 { 1494 return it; 1495 } 1496 int nlPos = ucFrameName.indexOf(QLatin1Char('\n')); 1497 #if QT_VERSION >= 0x060000 1498 if (nlPos > 0 && ucName == ucFrameName.mid(nlPos + 1, len)) 1499 #else 1500 if (nlPos > 0 && ucName == ucFrameName.midRef(nlPos + 1, len)) 1501 #endif 1502 { 1503 // Description in TXXX, WXXX, COMM, PRIV matches 1504 return it; 1505 } 1506 } 1507 } 1508 return it; 1509 } 1510 1511 /** 1512 * Find a frame by name. 1513 * 1514 * @param name the name of the frame to find, if the exact name is not 1515 * found, a case-insensitive search for the first name 1516 * starting with this string is performed 1517 * @param index 0 for first frame with @a name, 1 for second, etc. 1518 * 1519 * @return iterator or end() if not found. 1520 */ 1521 FrameCollection::const_iterator FrameCollection::findByName( 1522 const QString& name, int index) const 1523 { 1524 Frame frame(Frame::ExtendedType(name), QLatin1String(""), -1); 1525 auto it = find(frame); 1526 if (it == cend()) { 1527 it = searchByName(name); 1528 if (it == cend()) { 1529 const auto ids = getDisplayNamesOfIds().keys(name.toLatin1()); 1530 for (const QByteArray& id : ids) { 1531 if (!id.isEmpty()) { 1532 it = searchByName(QString::fromLatin1(id)); 1533 if (it != cend()) { 1534 break; 1535 } 1536 } 1537 } 1538 } 1539 } 1540 if (index > 0 && it != cend()) { 1541 const Frame::ExtendedType extendedType = it->getExtendedType(); 1542 for (int i = 0; i < index && it != cend(); ++i, ++it) {} 1543 if (it != cend() && !(it->getExtendedType() == extendedType)) { 1544 it = cend(); 1545 } 1546 } 1547 return it; 1548 } 1549 1550 /** 1551 * Find a frame by type or name. 1552 * 1553 * @param type type and name of the frame to find, if the exact name is not 1554 * found, a case-insensitive search for the first name 1555 * starting with this string is performed 1556 * @param index 0 for first frame with @a type, 1 for second, etc. 1557 * 1558 * @return iterator or end() if not found. 1559 */ 1560 FrameCollection::const_iterator FrameCollection::findByExtendedType( 1561 const Frame::ExtendedType& type, int index) const 1562 { 1563 Frame frame(type, QLatin1String(""), -1); 1564 auto it = find(frame); 1565 if (it == cend()) { 1566 it = searchByName(frame.getInternalName()); 1567 } 1568 if (index > 0 && it != cend()) { 1569 const Frame::ExtendedType extendedType = it->getExtendedType(); 1570 for (int i = 0; i < index && it != cend(); ++i, ++it) {} 1571 if (it != cend() && !(it->getExtendedType() == extendedType)) { 1572 it = cend(); 1573 } 1574 } 1575 return it; 1576 } 1577 1578 /** 1579 * Find a frame by index. 1580 * 1581 * @param index the index in the frame, see \ref Frame::getIndex() 1582 * 1583 * @return iterator or end() if not found. 1584 */ 1585 FrameCollection::const_iterator FrameCollection::findByIndex(int index) const 1586 { 1587 const_iterator it; 1588 for (it = cbegin(); it != cend(); ++it) { 1589 if (it->getIndex() == index) { 1590 break; 1591 } 1592 } 1593 return it; 1594 } 1595 1596 /** 1597 * Get value by type. 1598 * 1599 * @param type type 1600 * 1601 * @return value, QString::null if not found. 1602 */ 1603 QString FrameCollection::getValue(Frame::Type type) const 1604 { 1605 Frame frame(type, QLatin1String(""), QLatin1String(""), -1); 1606 auto it = find(frame); 1607 return it != cend() ? it->getValue() : QString(); 1608 } 1609 1610 /** 1611 * Get value by type and name. 1612 * 1613 * @param type type and name of the frame to find, if the exact name is not 1614 * found, a case-insensitive search for the first name 1615 * starting with this string is performed 1616 * 1617 * @return value, QString::null if not found. 1618 */ 1619 QString FrameCollection::getValue(const Frame::ExtendedType& type) const 1620 { 1621 auto it = findByExtendedType(type); 1622 return it != cend() ? it->getValue() : QString(); 1623 } 1624 1625 /** 1626 * Set value by type. 1627 * 1628 * @param type type 1629 * @param value value, nothing is done if QString::null 1630 */ 1631 void FrameCollection::setValue(Frame::Type type, const QString& value) 1632 { 1633 if (!value.isNull()) { 1634 Frame frame(type, QLatin1String(""), QLatin1String(""), -1); 1635 if (auto it = find(frame); it != end()) { 1636 auto& frameFound = const_cast<Frame&>(*it); 1637 frameFound.setValueIfChanged(value); 1638 } else { 1639 frame.setValueIfChanged(value); 1640 insert(frame); 1641 } 1642 } 1643 } 1644 1645 /** 1646 * Set value by type and name. 1647 * 1648 * @param type type and name of the frame to find, if the exact name is not 1649 * found, a case-insensitive search for the first name 1650 * starting with this string is performed 1651 * @param value value, nothing is done if QString::null 1652 */ 1653 void FrameCollection::setValue(const Frame::ExtendedType& type, 1654 const QString& value) 1655 { 1656 if (!value.isNull()) { 1657 Frame frame(type, QLatin1String(""), -1); 1658 auto it = find(frame); 1659 if (it == end()) { 1660 it = searchByName(type.getInternalName()); 1661 } 1662 if (it != end()) { 1663 auto& frameFound = const_cast<Frame&>(*it); 1664 frameFound.setValueIfChanged(value); 1665 } else { 1666 frame.setValueIfChanged(value); 1667 insert(frame); 1668 } 1669 } 1670 } 1671 1672 /** 1673 * Get integer value by type. 1674 * 1675 * @param type type 1676 * 1677 * @return value, 0 if empty, -1 if not found. 1678 */ 1679 int FrameCollection::getIntValue(Frame::Type type) const 1680 { 1681 QString str = getValue(type); 1682 return str.isNull() ? -1 : str.toInt(); 1683 } 1684 1685 /** 1686 * Set integer value by type. 1687 * 1688 * @param type type 1689 * @param value value, 0 to set empty, nothing is done if -1 1690 */ 1691 void FrameCollection::setIntValue(Frame::Type type, int value) 1692 { 1693 if (value != -1) { 1694 QString str = value != 0 ? QString::number(value) : QLatin1String(""); 1695 setValue(type, str); 1696 } 1697 } 1698 1699 /** 1700 * Compare the frames with another frame collection and mark the value as 1701 * changed on frames which are different. 1702 * 1703 * @param other other frame collection 1704 */ 1705 void FrameCollection::markChangedFrames(const FrameCollection& other) 1706 { 1707 for (auto it = begin(); it != end(); ++it) { 1708 auto otherIt = it->getIndex() != -1 1709 ? other.findByIndex(it->getIndex()) 1710 : other.find(*it); 1711 auto& frame = const_cast<Frame&>(*it); 1712 frame.setValueChanged(!(otherIt != other.cend() && otherIt->isEqual(*it))); 1713 } 1714 } 1715 1716 #ifndef QT_NO_DEBUG 1717 /** 1718 * Dump contents of frame collection to debug console. 1719 */ 1720 void FrameCollection::dump() const 1721 { 1722 qDebug("FrameCollection:"); 1723 for (auto it = cbegin(); it != cend(); ++it) { 1724 it->dump(); 1725 } 1726 } 1727 #endif 1728 1729 /** 1730 * Create a frame collection from a list of subframe fields. 1731 * 1732 * The given subframe fields must start with a Frame::ID_Subframe field with 1733 * the frame name as its value, followed by the fields of the frame. More 1734 * subframes may follow. 1735 * 1736 * @param begin iterator to begin of subframes 1737 * @param end iterator after end of subframes 1738 * 1739 * @return frames constructed from subframe fields. 1740 */ 1741 FrameCollection FrameCollection::fromSubframes( 1742 Frame::FieldList::const_iterator begin, // clazy:exclude=function-args-by-ref 1743 Frame::FieldList::const_iterator end) // clazy:exclude=function-args-by-ref 1744 { 1745 FrameCollection frames; 1746 Frame frame; 1747 int index = 0; 1748 for (auto it = begin; it != end; ++it) { 1749 if (const Frame::Field& fld = *it; fld.m_id == Frame::ID_Subframe) { 1750 if (frame.getType() != Frame::FT_UnknownFrame) { 1751 frame.setValueFromFieldList(); 1752 frames.insert(frame); 1753 frame = Frame(); 1754 } 1755 if (QString name = fld.m_value.toString(); !name.isEmpty()) { 1756 frame.setExtendedType(Frame::ExtendedType(name)); 1757 frame.setIndex(index++); 1758 } 1759 } else { 1760 if (frame.getType() != Frame::FT_UnknownFrame) { 1761 frame.fieldList().append(fld); 1762 } 1763 } 1764 } 1765 if (frame.getType() != Frame::FT_UnknownFrame) { 1766 frame.setValueFromFieldList(); 1767 frames.insert(frame); 1768 } 1769 return frames; 1770 } 1771 1772 1773 /** 1774 * Constructor. 1775 * All frames are disabled 1776 */ 1777 FrameFilter::FrameFilter() : m_enabledFrames(0) {} 1778 1779 /** 1780 * Enable all frames. 1781 */ 1782 void FrameFilter::enableAll() 1783 { 1784 m_enabledFrames = FTM_AllFrames; 1785 m_disabledOtherFrames.clear(); 1786 } 1787 1788 /** 1789 * Check if all fields are true. 1790 * 1791 * @return true if all fields are true. 1792 */ 1793 bool FrameFilter::areAllEnabled() const 1794 { 1795 return (m_enabledFrames & FTM_AllFrames) == FTM_AllFrames && 1796 m_disabledOtherFrames.empty(); 1797 } 1798 1799 /** 1800 * Check if frame is enabled. 1801 * 1802 * @param type frame type 1803 * @param name frame name 1804 * 1805 * @return true if frame is enabled. 1806 */ 1807 bool FrameFilter::isEnabled(Frame::Type type, const QString& name) const 1808 { 1809 if (type <= Frame::FT_LastFrame) { 1810 return (m_enabledFrames & (1ULL << type)) != 0; 1811 } 1812 if (!name.isEmpty()) { 1813 auto it = m_disabledOtherFrames.find(name); 1814 return it == m_disabledOtherFrames.end(); 1815 } 1816 return true; 1817 } 1818 1819 /** 1820 * Enable or disable frame. 1821 * 1822 * @param type frame type 1823 * @param name frame name 1824 * @param en true to enable 1825 */ 1826 void FrameFilter::enable(Frame::Type type, const QString& name, bool en) 1827 { 1828 if (type <= Frame::FT_LastFrame) { 1829 if (en) { 1830 m_enabledFrames |= (1ULL << type); 1831 } else { 1832 m_enabledFrames &= ~(1ULL << type); 1833 } 1834 } else if (!name.isEmpty()) { 1835 if (en) { 1836 if (auto it = m_disabledOtherFrames.find(name); 1837 it != m_disabledOtherFrames.end()) { 1838 m_disabledOtherFrames.erase(it); 1839 } 1840 } else { 1841 m_disabledOtherFrames.insert(name); 1842 } 1843 } 1844 } 1845 1846 1847 /** 1848 * Constructor. 1849 * 1850 * @param frames frame collection 1851 * @param str string with format codes 1852 */ 1853 FrameFormatReplacer::FrameFormatReplacer( 1854 const FrameCollection& frames, const QString& str) 1855 : FormatReplacer(str), m_frames(frames) {} 1856 1857 /** 1858 * Replace a format code (one character %c or multiple characters %{chars}). 1859 * Supported format fields: 1860 * %s title (song) 1861 * %l album 1862 * %a artist 1863 * %c comment 1864 * %y year 1865 * %t track, two digits, i.e. leading zero if < 10 1866 * %T track, without leading zeroes 1867 * %g genre 1868 * 1869 * @param code format code 1870 * 1871 * @return replacement string, 1872 * QString::null if code not found. 1873 */ 1874 QString FrameFormatReplacer::getReplacement(const QString& code) const 1875 { 1876 QString result; 1877 QString name; 1878 1879 if (code.length() == 1) { 1880 static const struct { 1881 const char* longCode; 1882 char shortCode; 1883 } shortToLong[] = { 1884 { "title", 's' }, 1885 { "album", 'l' }, 1886 { "artist", 'a' }, 1887 { "comment", 'c' }, 1888 { "year", 'y' }, 1889 { "track", 't' }, 1890 { "tracknumber", 'T' }, 1891 { "genre", 'g' } 1892 }; 1893 const char c = code[0].toLatin1(); 1894 for (const auto& [longCode, shortCode] : shortToLong) { 1895 if (shortCode == c) { 1896 name = QString::fromLatin1(longCode); 1897 break; 1898 } 1899 } 1900 } else if (code.length() > 1) { 1901 name = code; 1902 } 1903 1904 if (!name.isNull()) { 1905 QString lcName(name.toLower()); 1906 QString fieldName; 1907 int fieldWidth = lcName == QLatin1String("track") ? 2 : -1; 1908 if (lcName == QLatin1String("year")) { 1909 name = QLatin1String("date"); 1910 } else if (lcName == QLatin1String("tracknumber")) { 1911 name = QLatin1String("track number"); 1912 } 1913 if (int len = lcName.length(); 1914 len > 2 && lcName.at(len - 2) == QLatin1Char('.') && 1915 lcName.at(len - 1) >= QLatin1Char('0') && 1916 lcName.at(len - 1) <= QLatin1Char('9')) { 1917 fieldWidth = lcName.at(len - 1).toLatin1() - '0'; 1918 lcName.truncate(len - 2); 1919 name.truncate(len - 2); 1920 } 1921 if (const int dotIndex = name.indexOf(QLatin1Char('.')); dotIndex != -1) { 1922 fieldName = name.mid(dotIndex + 1); 1923 name.truncate(dotIndex); 1924 } 1925 1926 if (name == QLatin1String("disk")) { 1927 name = QLatin1String("disc number"); 1928 } 1929 1930 if (auto it = m_frames.findByName(name); it != m_frames.cend()) { 1931 if (fieldName.isEmpty()) { 1932 result = it->getValue().trimmed(); 1933 } else { 1934 result = Frame::getField(*it, fieldName).toString().trimmed(); 1935 } 1936 if (result.isNull()) { 1937 // code was found, but value is empty 1938 result = QLatin1String(""); 1939 } 1940 if (it->getType() == Frame::FT_Picture && result.isEmpty()) { 1941 if (QVariant fieldValue = it->getFieldValue(Frame::ID_Data); 1942 fieldValue.isValid() && fieldValue.toByteArray().size() > 0) { 1943 // If there is a picture without description, return "1", so that 1944 // an empty value indicates "no picture" 1945 result = QLatin1String("1"); 1946 } 1947 } 1948 } 1949 1950 if (lcName == QLatin1String("year")) { 1951 QRegularExpression yearRe(QLatin1String("^\\d{4}-\\d{2}")); 1952 if (auto match = yearRe.match(result); match.hasMatch()) { 1953 result.truncate(4); 1954 } 1955 } 1956 1957 if (fieldWidth > 0) { 1958 bool ok; 1959 int nr = Frame::numberWithoutTotal(result, &ok); 1960 if (ok) { 1961 result = QString(QLatin1String("%1")) 1962 .arg(nr, fieldWidth, 10, QLatin1Char('0')); 1963 } 1964 } 1965 } 1966 1967 return result; 1968 } 1969 1970 /** 1971 * Get help text for supported format codes. 1972 * 1973 * @param onlyRows if true only the tr elements are returned, 1974 * not the surrounding table 1975 * 1976 * @return help text. 1977 */ 1978 QString FrameFormatReplacer::getToolTip(bool onlyRows) 1979 { 1980 QString str; 1981 if (!onlyRows) str += QLatin1String("<table>\n"); 1982 1983 str += QLatin1String("<tr><td>%s</td><td>%{title}</td><td>"); 1984 str += QCoreApplication::translate("@default", "Title"); 1985 str += QLatin1String("</td></tr>\n"); 1986 1987 str += QLatin1String("<tr><td>%l</td><td>%{album}</td><td>"); 1988 str += QCoreApplication::translate("@default", "Album"); 1989 str += QLatin1String("</td></tr>\n"); 1990 1991 str += QLatin1String("<tr><td>%a</td><td>%{artist}</td><td>"); 1992 str += QCoreApplication::translate("@default", "Artist"); 1993 str += QLatin1String("</td></tr>\n"); 1994 1995 str += QLatin1String("<tr><td>%c</td><td>%{comment}</td><td>"); 1996 str += QCoreApplication::translate("@default", "Comment"); 1997 str += QLatin1String("</td></tr>\n"); 1998 1999 str += QLatin1String("<tr><td>%y</td><td>%{year}</td><td>"); 2000 const char* const yearStr = QT_TRANSLATE_NOOP("@default", "Year"); 2001 str += QCoreApplication::translate("@default", yearStr); 2002 str += QLatin1String("</td></tr>\n"); 2003 2004 str += QLatin1String("<tr><td>%t</td><td>%{track}</td><td>"); 2005 const char* const trackStr = QT_TRANSLATE_NOOP("@default", "Track"); 2006 str += QCoreApplication::translate("@default", trackStr); 2007 str += QLatin1String(" "01"</td></tr>\n"); 2008 2009 str += QLatin1String("<tr><td>%t</td><td>%{track.3}</td><td>"); 2010 str += QCoreApplication::translate("@default", trackStr); 2011 str += QLatin1String(" "001"</td></tr>\n"); 2012 2013 str += QLatin1String("<tr><td>%T</td><td>%{tracknumber}</td><td>"); 2014 str += QCoreApplication::translate("@default", trackStr); 2015 str += QLatin1String(" "1"</td></tr>\n"); 2016 2017 str += QLatin1String("<tr><td>%g</td><td>%{genre}</td><td>"); 2018 str += QCoreApplication::translate("@default", "Genre"); 2019 str += QLatin1String("</td></tr>\n"); 2020 2021 str += QLatin1String(R"(<tr><td></td><td>%{"t1"title"t2"}...</td><td>)"); 2022 const char* const prependAppendStr = 2023 QT_TRANSLATE_NOOP("@default", "Prepend t1/append t2 if not empty"); 2024 str += QCoreApplication::translate("@default", prependAppendStr); 2025 str += QLatin1String("</td></tr>\n"); 2026 2027 if (!onlyRows) str += QLatin1String("</table>\n"); 2028 return str; 2029 }