File indexing completed on 2024-05-19 04:55:59
0001 /** 0002 * \file trackdatamatcher.cpp 0003 * Shuffle imported tracks to optimize match with length, track or title. 0004 * 0005 * \b Project: Kid3 0006 * \author Urs Fleisch 0007 * \date 19 Jun 2011 0008 * 0009 * Copyright (C) 2011-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 "trackdatamatcher.h" 0028 #include <QDir> 0029 #include <climits> 0030 #include "trackdatamodel.h" 0031 0032 /** 0033 * Match import data with length. 0034 * 0035 * @param trackDataModel tracks to match 0036 * @param diffCheckEnable true if time difference check is enabled 0037 * @param maxDiff maximum allowed time difference 0038 */ 0039 bool TrackDataMatcher::matchWithLength(TrackDataModel* trackDataModel, 0040 bool diffCheckEnable, int maxDiff) 0041 { 0042 struct MatchData { 0043 int fileLen; // length of file 0044 int importLen; // length of import 0045 int assignedTo; // number of file import is assigned to, -1 if not assigned 0046 int assignedFrom; // number of import assigned to file, -1 if not assigned 0047 }; 0048 0049 bool failed = false; 0050 ImportTrackDataVector trackDataVector(trackDataModel->getTrackData()); 0051 if (const int numTracks = trackDataVector.size(); numTracks > 0) { 0052 auto md = new MatchData[numTracks]; 0053 int numFiles = 0, numImports = 0; 0054 int i = 0; 0055 for (auto it = trackDataVector.constBegin(); 0056 it != trackDataVector.constEnd(); 0057 ++it) { 0058 if (i >= numTracks) { 0059 break; 0060 } 0061 md[i].fileLen = it->getFileDuration(); 0062 if (md[i].fileLen > 0) { 0063 ++numFiles; 0064 } 0065 md[i].importLen = it->getImportDuration(); 0066 if (md[i].importLen > 0) { 0067 ++numImports; 0068 } 0069 md[i].assignedTo = -1; 0070 md[i].assignedFrom = -1; 0071 // If time difference checking is enabled and the time difference 0072 // is not larger then the allowed limit, do not reassign the track. 0073 if (diffCheckEnable) { 0074 if (md[i].fileLen != 0 && md[i].importLen != 0) { 0075 if (int diff = md[i].fileLen > md[i].importLen 0076 ? md[i].fileLen - md[i].importLen 0077 : md[i].importLen - md[i].fileLen; 0078 diff <= maxDiff) { 0079 md[i].assignedTo = i; 0080 md[i].assignedFrom = i; 0081 } 0082 } 0083 } 0084 ++i; 0085 } 0086 0087 if (numFiles <= numImports) { 0088 // more imports than files => first look through all imports 0089 for (i = 0; i < numTracks; ++i) { 0090 if (md[i].assignedFrom == -1) { 0091 int bestTrack = -1; 0092 int bestDiff = INT_MAX; 0093 // Find the unassigned import with the best difference 0094 for (int comparedTrack = 0; comparedTrack < numTracks; ++comparedTrack) { 0095 if (md[comparedTrack].assignedTo == -1) { 0096 if (int comparedDiff = md[i].fileLen > md[comparedTrack].importLen 0097 ? md[i].fileLen - md[comparedTrack].importLen 0098 : md[comparedTrack].importLen - md[i].fileLen; 0099 comparedDiff < bestDiff) { 0100 bestDiff = comparedDiff; 0101 bestTrack = comparedTrack; 0102 } 0103 } 0104 } 0105 if (bestTrack >= 0 && bestTrack < static_cast<int>(numTracks)) { 0106 md[i].assignedFrom = bestTrack; 0107 md[bestTrack].assignedTo = i; 0108 } else { 0109 qDebug("No match for track %d", i); 0110 failed = true; 0111 break; 0112 } 0113 } 0114 } 0115 } else { 0116 // more files than imports => first look through all files 0117 for (i = 0; i < numTracks; ++i) { 0118 if (md[i].assignedTo == -1) { 0119 int bestTrack = -1; 0120 int bestDiff = INT_MAX; 0121 // Find the unassigned file with the best difference 0122 for (int comparedTrack = 0; comparedTrack < numTracks; ++comparedTrack) { 0123 if (md[comparedTrack].assignedFrom == -1) { 0124 if (int comparedDiff = md[comparedTrack].fileLen > md[i].importLen 0125 ? md[comparedTrack].fileLen - md[i].importLen 0126 : md[i].importLen - md[comparedTrack].fileLen; 0127 comparedDiff < bestDiff) { 0128 bestDiff = comparedDiff; 0129 bestTrack = comparedTrack; 0130 } 0131 } 0132 } 0133 if (bestTrack >= 0 && bestTrack < static_cast<int>(numTracks)) { 0134 md[i].assignedTo = bestTrack; 0135 md[bestTrack].assignedFrom = i; 0136 } else { 0137 qDebug("No match for track %d", i); 0138 failed = true; 0139 break; 0140 } 0141 } 0142 } 0143 } 0144 0145 if (!failed) { 0146 ImportTrackDataVector oldTrackDataVector(trackDataVector); 0147 for (i = 0; i < numTracks; ++i) { 0148 trackDataVector[i].setFrameCollection( 0149 oldTrackDataVector[md[i].assignedFrom].getFrameCollection()); 0150 trackDataVector[i].setImportDuration( 0151 oldTrackDataVector[md[i].assignedFrom].getImportDuration()); 0152 } 0153 trackDataModel->setTrackData(trackDataVector); 0154 } 0155 0156 delete [] md; 0157 } 0158 return !failed; 0159 } 0160 0161 /** 0162 * Match import data with track number. 0163 * 0164 * @param trackDataModel tracks to match 0165 */ 0166 bool TrackDataMatcher::matchWithTrack(TrackDataModel* trackDataModel) 0167 { 0168 struct MatchData { 0169 int track; // track number starting with 0 0170 int assignedTo; // number of file import is assigned to, -1 if not assigned 0171 int assignedFrom; // number of import assigned to file, -1 if not assigned 0172 }; 0173 0174 bool failed = false; 0175 ImportTrackDataVector trackDataVector(trackDataModel->getTrackData()); 0176 if (const int numTracks = trackDataVector.size(); numTracks > 0) { 0177 auto md = new MatchData[numTracks]; 0178 0179 // 1st pass: Get track data and keep correct assignments. 0180 int i = 0; 0181 for (auto it = trackDataVector.constBegin(); 0182 it != trackDataVector.constEnd(); 0183 ++it) { 0184 if (i >= numTracks) { 0185 break; 0186 } 0187 if (it->getTrack() > 0 && it->getTrack() <= static_cast<int>(numTracks)) { 0188 md[i].track = it->getTrack() - 1; 0189 } else { 0190 md[i].track = -1; 0191 } 0192 md[i].assignedTo = -1; 0193 md[i].assignedFrom = -1; 0194 if (md[i].track == i) { 0195 md[i].assignedTo = i; 0196 md[i].assignedFrom = i; 0197 } 0198 ++i; 0199 } 0200 0201 // 2nd pass: Assign imported track numbers to unassigned tracks. 0202 for (i = 0; i < numTracks; ++i) { 0203 if (md[i].assignedTo == -1 && 0204 md[i].track >= 0 && md[i].track < static_cast<int>(numTracks)) { 0205 if (md[md[i].track].assignedFrom == -1) { 0206 md[md[i].track].assignedFrom = i; 0207 md[i].assignedTo = md[i].track; 0208 } 0209 } 0210 } 0211 0212 // 3rd pass: Assign remaining tracks. 0213 int unassignedTrack = 0; 0214 for (i = 0; i < numTracks; ++i) { 0215 if (md[i].assignedFrom == -1) { 0216 while (unassignedTrack < numTracks) { 0217 if (md[unassignedTrack].assignedTo == -1) { 0218 md[i].assignedFrom = unassignedTrack; 0219 md[unassignedTrack++].assignedTo = i; 0220 break; 0221 } 0222 ++unassignedTrack; 0223 } 0224 if (md[i].assignedFrom == -1) { 0225 qDebug("No track assigned to %d", i); 0226 failed = true; 0227 } 0228 } 0229 } 0230 0231 if (!failed) { 0232 ImportTrackDataVector oldTrackDataVector(trackDataVector); 0233 for (i = 0; i < numTracks; ++i) { 0234 trackDataVector[i].setFrameCollection( 0235 oldTrackDataVector[md[i].assignedFrom].getFrameCollection()); 0236 trackDataVector[i].setImportDuration( 0237 oldTrackDataVector[md[i].assignedFrom].getImportDuration()); 0238 } 0239 trackDataModel->setTrackData(trackDataVector); 0240 } 0241 0242 delete [] md; 0243 } 0244 return !failed; 0245 } 0246 0247 /** 0248 * Match import data with title. 0249 * 0250 * @param trackDataModel tracks to match 0251 */ 0252 bool TrackDataMatcher::matchWithTitle(TrackDataModel* trackDataModel) 0253 { 0254 struct MatchData { 0255 QSet<QString> fileWords; // words in file name 0256 QSet<QString> titleWords; // words in title 0257 int assignedTo = -1; // number of file import is assigned to, -1 if not assigned 0258 int assignedFrom = -1; // number of import assigned to file, -1 if not assigned 0259 }; 0260 0261 bool failed = false; 0262 ImportTrackDataVector trackDataVector(trackDataModel->getTrackData()); 0263 if (const int numTracks = trackDataVector.size(); numTracks > 0) { 0264 auto md = new MatchData[numTracks]; 0265 int numFiles = 0, numImports = 0; 0266 int i = 0; 0267 for (auto it = trackDataVector.constBegin(); 0268 it != trackDataVector.constEnd(); 0269 ++it) { 0270 if (i >= numTracks) { 0271 break; 0272 } 0273 md[i].fileWords = it->getFilenameWords(); 0274 if (!md[i].fileWords.isEmpty()) { 0275 ++numFiles; 0276 } 0277 md[i].titleWords = it->getTitleWords(); 0278 if (!md[i].titleWords.isEmpty()) { 0279 ++numImports; 0280 } 0281 md[i].assignedTo = -1; 0282 md[i].assignedFrom = -1; 0283 ++i; 0284 } 0285 0286 if (numFiles <= numImports) { 0287 // more imports than files => first look through all imports 0288 for (i = 0; i < numTracks; ++i) { 0289 if (md[i].assignedFrom == -1) { 0290 int bestTrack = -1; 0291 int bestMatch = -1; 0292 // Find the unassigned import with the best match 0293 for (int comparedTrack = 0; comparedTrack < numTracks; ++comparedTrack) { 0294 if (md[comparedTrack].assignedTo == -1) { 0295 if (int comparedMatch = 0296 (md[i].fileWords & md[comparedTrack].titleWords).size(); 0297 comparedMatch > bestMatch) { 0298 bestMatch = comparedMatch; 0299 bestTrack = comparedTrack; 0300 } 0301 } 0302 } 0303 if (bestTrack >= 0 && bestTrack < static_cast<int>(numTracks)) { 0304 md[i].assignedFrom = bestTrack; 0305 md[bestTrack].assignedTo = i; 0306 } else { 0307 qDebug("No match for track %d", i); 0308 failed = true; 0309 break; 0310 } 0311 } 0312 } 0313 } else { 0314 // more files than imports => first look through all files 0315 for (i = 0; i < numTracks; ++i) { 0316 if (md[i].assignedTo == -1) { 0317 int bestTrack = -1; 0318 int bestMatch = -1; 0319 // Find the unassigned file with the best match 0320 for (int comparedTrack = 0; comparedTrack < numTracks; ++comparedTrack) { 0321 if (md[comparedTrack].assignedFrom == -1) { 0322 if (int comparedMatch = 0323 (md[comparedTrack].fileWords & md[i].titleWords).size(); 0324 comparedMatch > bestMatch) { 0325 bestMatch = comparedMatch; 0326 bestTrack = comparedTrack; 0327 } 0328 } 0329 } 0330 if (bestTrack >= 0 && bestTrack < static_cast<int>(numTracks)) { 0331 md[i].assignedTo = bestTrack; 0332 md[bestTrack].assignedFrom = i; 0333 } else { 0334 qDebug("No match for track %d", i); 0335 failed = true; 0336 break; 0337 } 0338 } 0339 } 0340 } 0341 if (!failed) { 0342 ImportTrackDataVector oldTrackDataVector(trackDataVector); 0343 for (i = 0; i < numTracks; ++i) { 0344 trackDataVector[i].setFrameCollection( 0345 oldTrackDataVector[md[i].assignedFrom].getFrameCollection()); 0346 trackDataVector[i].setImportDuration( 0347 oldTrackDataVector[md[i].assignedFrom].getImportDuration()); 0348 } 0349 trackDataModel->setTrackData(trackDataVector); 0350 } 0351 0352 delete [] md; 0353 } 0354 return !failed; 0355 }