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