1 /**
2  * \file trackdatamatcher.cpp
3  * Shuffle imported tracks to optimize match with length, track or title.
4  *
5  * \b Project: Kid3
6  * \author Urs Fleisch
7  * \date 19 Jun 2011
8  *
9  * Copyright (C) 2011-2018  Urs Fleisch
10  *
11  * This file is part of Kid3.
12  *
13  * Kid3 is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * Kid3 is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
25  */
26 
27 #include "trackdatamatcher.h"
28 #include <QDir>
29 #include <climits>
30 #include "trackdatamodel.h"
31 
32 /**
33  * Match import data with length.
34  *
35  * @param trackDataModel tracks to match
36  * @param diffCheckEnable true if time difference check is enabled
37  * @param maxDiff maximum allowed time difference
38  */
matchWithLength(TrackDataModel * trackDataModel,bool diffCheckEnable,int maxDiff)39 bool TrackDataMatcher::matchWithLength(TrackDataModel* trackDataModel,
40                                        bool diffCheckEnable, int maxDiff)
41 {
42   struct MatchData {
43     int fileLen;      // length of file
44     int importLen;    // length of import
45     int assignedTo;   // number of file import is assigned to, -1 if not assigned
46     int assignedFrom; // number of import assigned to file, -1 if not assigned
47   };
48 
49   bool failed = false;
50   ImportTrackDataVector trackDataVector(trackDataModel->getTrackData());
51   const int numTracks = trackDataVector.size();
52   if (numTracks > 0) {
53     auto md = new MatchData[numTracks];
54     int numFiles = 0, numImports = 0;
55     int i = 0;
56     for (auto it = trackDataVector.constBegin();
57          it != trackDataVector.constEnd();
58          ++it) {
59       if (i >= numTracks) {
60         break;
61       }
62       md[i].fileLen = (*it).getFileDuration();
63       if (md[i].fileLen > 0) {
64         ++numFiles;
65       }
66       md[i].importLen = (*it).getImportDuration();
67       if (md[i].importLen > 0) {
68         ++numImports;
69       }
70       md[i].assignedTo = -1;
71       md[i].assignedFrom = -1;
72       // If time difference checking is enabled and the time difference
73       // is not larger then the allowed limit, do not reassign the track.
74       if (diffCheckEnable) {
75         if (md[i].fileLen != 0 && md[i].importLen != 0) {
76           int diff = md[i].fileLen > md[i].importLen ?
77             md[i].fileLen - md[i].importLen : md[i].importLen - md[i].fileLen;
78           if (diff <= maxDiff) {
79             md[i].assignedTo = i;
80             md[i].assignedFrom = i;
81           }
82         }
83       }
84       ++i;
85     }
86 
87     if (numFiles <= numImports) {
88       // more imports than files => first look through all imports
89       for (i = 0; i < numTracks; ++i) {
90         if (md[i].assignedFrom == -1) {
91           int bestTrack = -1;
92           int bestDiff = INT_MAX;
93           // Find the unassigned import with the best difference
94           for (int comparedTrack = 0; comparedTrack < numTracks; ++comparedTrack) {
95             if (md[comparedTrack].assignedTo == -1) {
96               int comparedDiff = md[i].fileLen > md[comparedTrack].importLen
97                   ? md[i].fileLen - md[comparedTrack].importLen
98                   : md[comparedTrack].importLen - md[i].fileLen;
99               if (comparedDiff < bestDiff) {
100                 bestDiff = comparedDiff;
101                 bestTrack = comparedTrack;
102               }
103             }
104           }
105           if (bestTrack >= 0 && bestTrack < static_cast<int>(numTracks)) {
106             md[i].assignedFrom = bestTrack;
107             md[bestTrack].assignedTo = i;
108           } else {
109             qDebug("No match for track %d", i);
110             failed = true;
111             break;
112           }
113         }
114       }
115     } else {
116       // more files than imports => first look through all files
117       for (i = 0; i < numTracks; ++i) {
118         if (md[i].assignedTo == -1) {
119           int bestTrack = -1;
120           int bestDiff = INT_MAX;
121           // Find the unassigned file with the best difference
122           for (int comparedTrack = 0; comparedTrack < numTracks; ++comparedTrack) {
123             if (md[comparedTrack].assignedFrom == -1) {
124               int comparedDiff = md[comparedTrack].fileLen > md[i].importLen
125                   ? md[comparedTrack].fileLen - md[i].importLen
126                   : md[i].importLen - md[comparedTrack].fileLen;
127               if (comparedDiff < bestDiff) {
128                 bestDiff = comparedDiff;
129                 bestTrack = comparedTrack;
130               }
131             }
132           }
133           if (bestTrack >= 0 && bestTrack < static_cast<int>(numTracks)) {
134             md[i].assignedTo = bestTrack;
135             md[bestTrack].assignedFrom = i;
136           } else {
137             qDebug("No match for track %d", i);
138             failed = true;
139             break;
140           }
141         }
142       }
143     }
144 
145     if (!failed) {
146       ImportTrackDataVector oldTrackDataVector(trackDataVector);
147       for (i = 0; i < numTracks; ++i) {
148         trackDataVector[i].setFrameCollection(
149           oldTrackDataVector[md[i].assignedFrom].getFrameCollection());
150         trackDataVector[i].setImportDuration(
151           oldTrackDataVector[md[i].assignedFrom].getImportDuration());
152       }
153       trackDataModel->setTrackData(trackDataVector);
154     }
155 
156     delete [] md;
157   }
158   return !failed;
159 }
160 
161 /**
162  * Match import data with track number.
163  *
164  * @param trackDataModel tracks to match
165  */
matchWithTrack(TrackDataModel * trackDataModel)166 bool TrackDataMatcher::matchWithTrack(TrackDataModel* trackDataModel)
167 {
168   struct MatchData {
169     int track;        // track number starting with 0
170     int assignedTo;   // number of file import is assigned to, -1 if not assigned
171     int assignedFrom; // number of import assigned to file, -1 if not assigned
172   };
173 
174   bool failed = false;
175   ImportTrackDataVector trackDataVector(trackDataModel->getTrackData());
176   const int numTracks = trackDataVector.size();
177   if (numTracks > 0) {
178     auto md = new MatchData[numTracks];
179 
180     // 1st pass: Get track data and keep correct assignments.
181     int i = 0;
182     for (auto it = trackDataVector.constBegin();
183          it != trackDataVector.constEnd();
184          ++it) {
185       if (i >= numTracks) {
186         break;
187       }
188       if ((*it).getTrack() > 0 && (*it).getTrack() <= static_cast<int>(numTracks)) {
189         md[i].track = (*it).getTrack() - 1;
190       } else {
191         md[i].track = -1;
192       }
193       md[i].assignedTo = -1;
194       md[i].assignedFrom = -1;
195       if (md[i].track == static_cast<int>(i)) {
196         md[i].assignedTo = i;
197         md[i].assignedFrom = i;
198       }
199       ++i;
200     }
201 
202     // 2nd pass: Assign imported track numbers to unassigned tracks.
203     for (i = 0; i < numTracks; ++i) {
204       if (md[i].assignedTo == -1 &&
205           md[i].track >= 0 && md[i].track < static_cast<int>(numTracks)) {
206         if (md[md[i].track].assignedFrom == -1) {
207           md[md[i].track].assignedFrom = i;
208           md[i].assignedTo = md[i].track;
209         }
210       }
211     }
212 
213     // 3rd pass: Assign remaining tracks.
214     int unassignedTrack = 0;
215     for (i = 0; i < numTracks; ++i) {
216       if (md[i].assignedFrom == -1) {
217         while (unassignedTrack < numTracks) {
218           if (md[unassignedTrack].assignedTo == -1) {
219             md[i].assignedFrom = unassignedTrack;
220             md[unassignedTrack++].assignedTo = i;
221             break;
222           }
223           ++unassignedTrack;
224         }
225         if (md[i].assignedFrom == -1) {
226           qDebug("No track assigned to %d", i);
227           failed = true;
228         }
229       }
230     }
231 
232     if (!failed) {
233       ImportTrackDataVector oldTrackDataVector(trackDataVector);
234       for (i = 0; i < numTracks; ++i) {
235         trackDataVector[i].setFrameCollection(
236           oldTrackDataVector[md[i].assignedFrom].getFrameCollection());
237         trackDataVector[i].setImportDuration(
238           oldTrackDataVector[md[i].assignedFrom].getImportDuration());
239       }
240       trackDataModel->setTrackData(trackDataVector);
241     }
242 
243     delete [] md;
244   }
245   return !failed;
246 }
247 
248 /**
249  * Match import data with title.
250  *
251  * @param trackDataModel tracks to match
252  */
matchWithTitle(TrackDataModel * trackDataModel)253 bool TrackDataMatcher::matchWithTitle(TrackDataModel* trackDataModel)
254 {
255   struct MatchData {
256     QSet<QString> fileWords;  // words in file name
257     QSet<QString> titleWords; // words in title
258     int assignedTo = -1;   // number of file import is assigned to, -1 if not assigned
259     int assignedFrom = -1; // number of import assigned to file, -1 if not assigned
260   };
261 
262   bool failed = false;
263   ImportTrackDataVector trackDataVector(trackDataModel->getTrackData());
264   const int numTracks = trackDataVector.size();
265   if (numTracks > 0) {
266     auto md = new MatchData[numTracks];
267     int numFiles = 0, numImports = 0;
268     int i = 0;
269     for (auto it = trackDataVector.constBegin();
270          it != trackDataVector.constEnd();
271          ++it) {
272       if (i >= numTracks) {
273         break;
274       }
275       md[i].fileWords = it->getFilenameWords();
276       if (!md[i].fileWords.isEmpty()) {
277         ++numFiles;
278       }
279       md[i].titleWords = it->getTitleWords();
280       if (!md[i].titleWords.isEmpty()) {
281         ++numImports;
282       }
283       md[i].assignedTo = -1;
284       md[i].assignedFrom = -1;
285       ++i;
286     }
287 
288     if (numFiles <= numImports) {
289       // more imports than files => first look through all imports
290       for (i = 0; i < numTracks; ++i) {
291         if (md[i].assignedFrom == -1) {
292           int bestTrack = -1;
293           int bestMatch = -1;
294           // Find the unassigned import with the best match
295           for (int comparedTrack = 0; comparedTrack < numTracks; ++comparedTrack) {
296             if (md[comparedTrack].assignedTo == -1) {
297               int comparedMatch =
298                   (md[i].fileWords & md[comparedTrack].titleWords).size();
299               if (comparedMatch > bestMatch) {
300                 bestMatch = comparedMatch;
301                 bestTrack = comparedTrack;
302               }
303             }
304           }
305           if (bestTrack >= 0 && bestTrack < static_cast<int>(numTracks)) {
306             md[i].assignedFrom = bestTrack;
307             md[bestTrack].assignedTo = i;
308           } else {
309             qDebug("No match for track %d", i);
310             failed = true;
311             break;
312           }
313         }
314       }
315     } else {
316       // more files than imports => first look through all files
317       for (i = 0; i < numTracks; ++i) {
318         if (md[i].assignedTo == -1) {
319           int bestTrack = -1;
320           int bestMatch = -1;
321           // Find the unassigned file with the best match
322           for (int comparedTrack = 0; comparedTrack < numTracks; ++comparedTrack) {
323             if (md[comparedTrack].assignedFrom == -1) {
324               int comparedMatch =
325                   (md[comparedTrack].fileWords & md[i].titleWords).size();
326               if (comparedMatch > bestMatch) {
327                 bestMatch = comparedMatch;
328                 bestTrack = comparedTrack;
329               }
330             }
331           }
332           if (bestTrack >= 0 && bestTrack < static_cast<int>(numTracks)) {
333             md[i].assignedTo = bestTrack;
334             md[bestTrack].assignedFrom = i;
335           } else {
336             qDebug("No match for track %d", i);
337             failed = true;
338             break;
339           }
340         }
341       }
342     }
343     if (!failed) {
344       ImportTrackDataVector oldTrackDataVector(trackDataVector);
345       for (i = 0; i < numTracks; ++i) {
346         trackDataVector[i].setFrameCollection(
347           oldTrackDataVector[md[i].assignedFrom].getFrameCollection());
348         trackDataVector[i].setImportDuration(
349           oldTrackDataVector[md[i].assignedFrom].getImportDuration());
350       }
351       trackDataModel->setTrackData(trackDataVector);
352     }
353 
354     delete [] md;
355   }
356   return !failed;
357 }
358