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 }