1 /*
2 SPDX-FileCopyrightText: 2018 (c) Matthieu Gallien <matthieu_gallien@yahoo.fr>
3
4 SPDX-License-Identifier: LGPL-3.0-or-later
5 */
6
7 #include "trackmetadatamodel.h"
8
9 #include "musiclistenersmanager.h"
10
11 #include <KI18n/KLocalizedString>
12
13 #include <QtConcurrent>
14
15 #include <algorithm>
16
TrackMetadataModel(QObject * parent)17 TrackMetadataModel::TrackMetadataModel(QObject *parent)
18 : QAbstractListModel(parent)
19 {
20 connect(&mLyricsValueWatcher, &QFutureWatcher<QString>::finished,
21 this, &TrackMetadataModel::lyricsValueIsReady);
22 }
23
~TrackMetadataModel()24 TrackMetadataModel::~TrackMetadataModel()
25 {
26 if (mLyricsValueWatcher.isRunning() && !mLyricsValueWatcher.isFinished()) {
27 mLyricsValueWatcher.waitForFinished();
28 }
29 }
30
rowCount(const QModelIndex & parent) const31 int TrackMetadataModel::rowCount(const QModelIndex &parent) const
32 {
33 if (parent.isValid()) {
34 return 0;
35 }
36
37 return mTrackKeys.count();
38 }
39
data(const QModelIndex & index,int role) const40 QVariant TrackMetadataModel::data(const QModelIndex &index, int role) const
41 {
42 auto result = QVariant{};
43
44 const auto currentKey = mTrackKeys[index.row()];
45
46 switch (role)
47 {
48 case Qt::DisplayRole:
49 switch (currentKey)
50 {
51 case DataTypes::TrackNumberRole:
52 {
53 auto trackNumber = mTrackData.trackNumber();
54 if (trackNumber > 0) {
55 result = trackNumber;
56 }
57 break;
58 }
59 case DataTypes::DiscNumberRole:
60 {
61 auto discNumber = mTrackData.discNumber();
62 if (discNumber > 0) {
63 result = discNumber;
64 }
65 break;
66 }
67 case DataTypes::ChannelsRole:
68 {
69 auto channels = mTrackData.channels();
70 if (channels > 0) {
71 result = channels;
72 }
73 break;
74 }
75 case DataTypes::BitRateRole:
76 {
77 auto bitRate = mTrackData.bitRate();
78 if (bitRate > 0) {
79 result = bitRate;
80 }
81 break;
82 }
83 case DataTypes::SampleRateRole:
84 {
85 auto sampleRate = mTrackData.sampleRate();
86 if (sampleRate > 0) {
87 result = sampleRate;
88 }
89 break;
90 }
91 case DataTypes::DurationRole:
92 {
93 auto trackDuration = mTrackData.duration();
94 if (trackDuration.hour() == 0) {
95 result = trackDuration.toString(QStringLiteral("mm:ss"));
96 } else {
97 result = trackDuration.toString();
98 }
99 break;
100 }
101 default:
102 result = mTrackData[currentKey];
103 break;
104 }
105 break;
106 case ItemNameRole:
107 result = nameFromRole(currentKey);
108 break;
109 case ItemTypeRole:
110 switch (currentKey)
111 {
112 case DataTypes::TitleRole:
113 result = TextEntry;
114 break;
115 case DataTypes::ResourceRole:
116 case DataTypes::ImageUrlRole:
117 result = UrlEntry;
118 break;
119 case DataTypes::ArtistRole:
120 result = TextEntry;
121 break;
122 case DataTypes::AlbumRole:
123 result = TextEntry;
124 break;
125 case DataTypes::AlbumArtistRole:
126 result = TextEntry;
127 break;
128 case DataTypes::TrackNumberRole:
129 result = IntegerEntry;
130 break;
131 case DataTypes::DiscNumberRole:
132 result = IntegerEntry;
133 break;
134 case DataTypes::RatingRole:
135 result = RatingEntry;
136 break;
137 case DataTypes::GenreRole:
138 result = TextEntry;
139 break;
140 case DataTypes::LyricistRole:
141 result = TextEntry;
142 break;
143 case DataTypes::ComposerRole:
144 result = TextEntry;
145 break;
146 case DataTypes::CommentRole:
147 result = TextEntry;
148 break;
149 case DataTypes::YearRole:
150 result = IntegerEntry;
151 break;
152 case DataTypes::LastPlayDate:
153 result = DateEntry;
154 break;
155 case DataTypes::PlayCounter:
156 result = IntegerEntry;
157 break;
158 case DataTypes::LyricsRole:
159 result = LongTextEntry;
160 break;
161 case DataTypes::SampleRateRole:
162 result = IntegerEntry;
163 break;
164 case DataTypes::BitRateRole:
165 result = IntegerEntry;
166 break;
167 case DataTypes::ChannelsRole:
168 result = IntegerEntry;
169 break;
170 case DataTypes::FirstPlayDate:
171 result = DateEntry;
172 break;
173 case DataTypes::DurationRole:
174 result = DurationEntry;
175 break;
176 case DataTypes::SecondaryTextRole:
177 case DataTypes::ShadowForImageRole:
178 case DataTypes::ChildModelRole:
179 case DataTypes::StringDurationRole:
180 case DataTypes::IsValidAlbumArtistRole:
181 case DataTypes::AllArtistsRole:
182 case DataTypes::HighestTrackRating:
183 case DataTypes::IdRole:
184 case DataTypes::ParentIdRole:
185 case DataTypes::DatabaseIdRole:
186 case DataTypes::IsSingleDiscAlbumRole:
187 case DataTypes::ContainerDataRole:
188 case DataTypes::IsPartialDataRole:
189 case DataTypes::AlbumIdRole:
190 case DataTypes::HasEmbeddedCover:
191 case DataTypes::FileModificationTime:
192 case DataTypes::PlayFrequency:
193 case DataTypes::ElementTypeRole:
194 case DataTypes::FullDataRole:
195 case DataTypes::IsDirectoryRole:
196 case DataTypes::IsPlayListRole:
197 case DataTypes::FilePathRole:
198 break;
199 }
200 break;
201 }
202
203 return result;
204 }
205
setData(const QModelIndex & index,const QVariant & value,int role)206 bool TrackMetadataModel::setData(const QModelIndex &index, const QVariant &value, int role)
207 {
208 if (data(index, role) != value) {
209 auto dataType = mTrackKeys[index.row()];
210
211 mTrackData[dataType] = value;
212 mFullData[dataType] = value;
213
214 Q_EMIT dataChanged(index, index, QVector<int>() << role);
215 return true;
216 }
217 return false;
218 }
219
roleNames() const220 QHash<int, QByteArray> TrackMetadataModel::roleNames() const
221 {
222 auto names = QAbstractListModel::roleNames();
223
224 names[ItemNameRole] = "name";
225 names[ItemTypeRole] = "type";
226
227 return names;
228 }
229
fileUrl() const230 QString TrackMetadataModel::fileUrl() const
231 {
232 if (mFileUrl.isLocalFile()) {
233 return mFileUrl.toLocalFile();
234 } else {
235 return mFileUrl.toString();
236 }
237 }
238
coverUrl() const239 QUrl TrackMetadataModel::coverUrl() const
240 {
241 if (mCoverImage.isEmpty()) {
242 return QUrl(QStringLiteral("image://icon/media-optical-audio"));
243 } else {
244 return mCoverImage;
245 }
246 }
247
manager() const248 MusicListenersManager *TrackMetadataModel::manager() const
249 {
250 return mManager;
251 }
252
lyrics() const253 QString TrackMetadataModel::lyrics() const
254 {
255 return mFullData[TrackDataType::key_type::LyricsRole].toString();
256 }
257
databaseId() const258 qulonglong TrackMetadataModel::databaseId() const
259 {
260 return mDatabaseId;
261 }
262
trackData(const TrackMetadataModel::TrackDataType & trackData)263 void TrackMetadataModel::trackData(const TrackMetadataModel::TrackDataType &trackData)
264 {
265 if ((mDatabaseId != 0 && trackData.databaseId() != mDatabaseId) ||
266 (!mFileUrl.isEmpty() && trackData.resourceURI() != mFileUrl) ||
267 (!mFullData.isEmpty() && trackData.databaseId() != mFullData.databaseId())) {
268 return;
269 }
270
271 const QList<DataTypes::ColumnsRoles> fieldsForTrack({DataTypes::TitleRole, DataTypes::ArtistRole,
272 DataTypes::AlbumRole, DataTypes::AlbumArtistRole,
273 DataTypes::TrackNumberRole, DataTypes::DiscNumberRole,
274 DataTypes::RatingRole, DataTypes::GenreRole,
275 DataTypes::LyricistRole, DataTypes::ComposerRole,
276 DataTypes::CommentRole, DataTypes::YearRole,
277 DataTypes::ChannelsRole, DataTypes::BitRateRole,
278 DataTypes::SampleRateRole, DataTypes::LyricsRole,
279 DataTypes::LastPlayDate, DataTypes::PlayCounter,
280 DataTypes::DurationRole});
281
282 fillDataFromTrackData(trackData, fieldsForTrack);
283 }
284
fillDataFromTrackData(const TrackMetadataModel::TrackDataType & trackData,const QList<DataTypes::ColumnsRoles> & fieldsForTrack)285 void TrackMetadataModel::fillDataFromTrackData(const TrackMetadataModel::TrackDataType &trackData,
286 const QList<DataTypes::ColumnsRoles> &fieldsForTrack)
287 {
288 beginResetModel();
289 mFullData = trackData;
290 mTrackData.clear();
291 mTrackKeys.clear();
292
293 for (DataTypes::ColumnsRoles role : fieldsForTrack) {
294 if (trackData.constFind(role) != trackData.constEnd()) {
295 mTrackKeys.push_back(role);
296 mTrackData[role] = trackData[role];
297 }
298 }
299 filterDataFromTrackData();
300 endResetModel();
301
302 if (trackData.hasDatabaseId()) {
303 fetchLyrics();
304 }
305
306 mDatabaseId = trackData[DataTypes::DatabaseIdRole].toULongLong();
307 Q_EMIT databaseIdChanged();
308
309 mCoverImage = trackData[DataTypes::ImageUrlRole].toUrl();
310 Q_EMIT coverUrlChanged();
311
312 auto rawFileUrl = trackData[DataTypes::ResourceRole].toUrl();
313
314 mFileUrl = rawFileUrl;
315 Q_EMIT fileUrlChanged();
316 }
317
filterDataFromTrackData()318 void TrackMetadataModel::filterDataFromTrackData()
319 {
320 }
321
removeMetaData(DataTypes::ColumnsRoles metaData)322 void TrackMetadataModel::removeMetaData(DataTypes::ColumnsRoles metaData)
323 {
324 auto itMetaData = std::find(mTrackKeys.begin(), mTrackKeys.end(), metaData);
325 if (itMetaData == mTrackKeys.end()) {
326 return;
327 }
328
329 mTrackKeys.erase(itMetaData);
330 mTrackData.remove(metaData);
331 }
332
dataFromType(TrackDataType::key_type metaData) const333 TrackMetadataModel::TrackDataType::mapped_type TrackMetadataModel::dataFromType(TrackDataType::key_type metaData) const
334 {
335 return mFullData[metaData];
336 }
337
fillLyricsDataFromTrack()338 void TrackMetadataModel::fillLyricsDataFromTrack()
339 {
340 beginInsertRows({}, mTrackData.size(), mTrackData.size());
341 mTrackKeys.push_back(DataTypes::LyricsRole);
342 mTrackData[DataTypes::LyricsRole] = mLyricsValueWatcher.result();
343 endInsertRows();
344 }
345
allTrackData() const346 const TrackMetadataModel::TrackDataType &TrackMetadataModel::allTrackData() const
347 {
348 return mFullData;
349 }
350
lyricsValueIsReady()351 void TrackMetadataModel::lyricsValueIsReady()
352 {
353 if (!mLyricsValueWatcher.result().isEmpty()) {
354 fillLyricsDataFromTrack();
355
356 mFullData[DataTypes::LyricsRole] = mLyricsValueWatcher.result();
357
358 Q_EMIT lyricsChanged();
359 }
360 }
361
initializeByIdAndUrl(ElisaUtils::PlayListEntryType type,qulonglong databaseId,const QUrl & url)362 void TrackMetadataModel::initializeByIdAndUrl(ElisaUtils::PlayListEntryType type, qulonglong databaseId, const QUrl &url)
363 {
364 if (!mFullData.isEmpty()) {
365 beginResetModel();
366 mFullData.clear();
367 mTrackData.clear();
368 mCoverImage.clear();
369 mFileUrl.clear();
370 endResetModel();
371
372 Q_EMIT lyricsChanged();
373 }
374
375 mFileUrl = url;
376 mDatabaseId = databaseId;
377
378 Q_EMIT needDataByDatabaseIdAndUrl(type, databaseId, url);
379 }
380
initialize(MusicListenersManager * newManager,DatabaseInterface * trackDatabase)381 void TrackMetadataModel::initialize(MusicListenersManager *newManager, DatabaseInterface *trackDatabase)
382 {
383 mManager = newManager;
384 Q_EMIT managerChanged();
385
386 if (mManager) {
387 mDataLoader.setDatabase(mManager->viewDatabase());
388 } else if (trackDatabase) {
389 mDataLoader.setDatabase(trackDatabase);
390 }
391
392 if (mManager) {
393 mManager->connectModel(&mDataLoader);
394 }
395
396 connect(this, &TrackMetadataModel::needDataByDatabaseIdAndUrl,
397 &mDataLoader, &ModelDataLoader::loadDataByDatabaseIdAndUrl);
398 connect(this, &TrackMetadataModel::needDataByUrl,
399 &mDataLoader, &ModelDataLoader::loadDataByUrl);
400 connect(&mDataLoader, &ModelDataLoader::trackModified,
401 this, &TrackMetadataModel::trackData);
402 connect(&mDataLoader, &ModelDataLoader::allTrackData,
403 this, &TrackMetadataModel::trackData);
404 connect(&mDataLoader, &ModelDataLoader::allRadioData,
405 this, &TrackMetadataModel::radioData);
406 connect(&mDataLoader, &ModelDataLoader::radioAdded,
407 this, &TrackMetadataModel::radioData);
408 connect(&mDataLoader, &ModelDataLoader::radioModified,
409 this, &TrackMetadataModel::radioData);
410 }
411
modelDataLoader()412 ModelDataLoader &TrackMetadataModel::modelDataLoader()
413 {
414 return mDataLoader;
415 }
416
displayedTrackData() const417 const TrackMetadataModel::TrackDataType &TrackMetadataModel::displayedTrackData() const
418 {
419 return mTrackData;
420 }
421
trackKey(int index) const422 DataTypes::ColumnsRoles TrackMetadataModel::trackKey(int index) const
423 {
424 return mTrackKeys[index];
425 }
426
removeDataByIndex(int index)427 void TrackMetadataModel::removeDataByIndex(int index)
428 {
429 auto dataKey = mTrackKeys[index];
430
431 mTrackData[dataKey] = {};
432 mFullData[dataKey] = {};
433 mTrackKeys.removeAt(index);
434 }
435
addDataByName(const QString & name)436 void TrackMetadataModel::addDataByName(const QString &name)
437 {
438 DataTypes::ColumnsRoles newRole = DataTypes::TitleRole;
439
440 if (name == i18nc("Track title for track metadata view", "Title")) {
441 newRole = DataTypes::TitleRole;
442 } else if (name == i18nc("Track artist for track metadata view", "Artist")) {
443 newRole = DataTypes::ArtistRole;
444 } else if (name == i18nc("Album name for track metadata view", "Album")) {
445 newRole = DataTypes::AlbumRole;
446 } else if (name == i18nc("Album artist for track metadata view", "Album Artist")) {
447 newRole = DataTypes::AlbumArtistRole;
448 } else if (name == i18nc("Track number for track metadata view", "Track Number")) {
449 newRole = DataTypes::TrackNumberRole;
450 } else if (name == i18nc("Disc number for track metadata view", "Disc Number")) {
451 newRole = DataTypes::DiscNumberRole;
452 } else if (name == i18nc("Rating label for information panel", "Rating")) {
453 newRole = DataTypes::RatingRole;
454 } else if (name == i18nc("Genre label for track metadata view", "Genre")) {
455 newRole = DataTypes::GenreRole;
456 } else if (name == i18nc("Lyricist label for track metadata view", "Lyricist")) {
457 newRole = DataTypes::LyricistRole;
458 } else if (name == i18nc("Composer name for track metadata view", "Composer")) {
459 newRole = DataTypes::ComposerRole;
460 } else if (name == i18nc("Comment label for track metadata view", "Comment")) {
461 newRole = DataTypes::CommentRole;
462 } else if (name == i18nc("Year label for track metadata view", "Year")) {
463 newRole = DataTypes::YearRole;
464 } else if (name == i18nc("Channels label for track metadata view", "Channels")) {
465 newRole = DataTypes::ChannelsRole;
466 } else if (name == i18nc("Bit rate label for track metadata view", "Bit Rate")) {
467 newRole = DataTypes::BitRateRole;
468 } else if (name == i18nc("Sample Rate label for track metadata view", "Sample Rate")) {
469 newRole = DataTypes::SampleRateRole;
470 } else if (name == i18nc("Lyrics label for track metadata view", "Lyrics")) {
471 newRole = DataTypes::LyricsRole;
472 } else if (name == i18nc("Duration label for track metadata view", "Duration")) {
473 newRole = DataTypes::DurationRole;
474 }
475
476 mTrackData[newRole] = {};
477 mFullData[newRole] = {};
478 mTrackKeys.push_back(newRole);
479 }
480
nameFromRole(DataTypes::ColumnsRoles role)481 QString TrackMetadataModel::nameFromRole(DataTypes::ColumnsRoles role)
482 {
483 auto result = QString{};
484 switch (role)
485 {
486 case DataTypes::TitleRole:
487 result = i18nc("Track title for track metadata view", "Title");
488 break;
489 case DataTypes::DurationRole:
490 result = i18nc("Duration label for track metadata view", "Duration");
491 break;
492 case DataTypes::ArtistRole:
493 result = i18nc("Track artist for track metadata view", "Artist");
494 break;
495 case DataTypes::AlbumRole:
496 result = i18nc("Album name for track metadata view", "Album");
497 break;
498 case DataTypes::AlbumArtistRole:
499 result = i18nc("Album artist for track metadata view", "Album Artist");
500 break;
501 case DataTypes::TrackNumberRole:
502 result = i18nc("Track number for track metadata view", "Track Number");
503 break;
504 case DataTypes::DiscNumberRole:
505 result = i18nc("Disc number for track metadata view", "Disc Number");
506 break;
507 case DataTypes::RatingRole:
508 result = i18nc("Rating label for information panel", "Rating");
509 break;
510 case DataTypes::GenreRole:
511 result = i18nc("Genre label for track metadata view", "Genre");
512 break;
513 case DataTypes::LyricistRole:
514 result = i18nc("Lyricist label for track metadata view", "Lyricist");
515 break;
516 case DataTypes::ComposerRole:
517 result = i18nc("Composer name for track metadata view", "Composer");
518 break;
519 case DataTypes::CommentRole:
520 result = i18nc("Comment label for track metadata view", "Comment");
521 break;
522 case DataTypes::YearRole:
523 result = i18nc("Year label for track metadata view", "Year");
524 break;
525 case DataTypes::ChannelsRole:
526 result = i18nc("Channels label for track metadata view", "Channels");
527 break;
528 case DataTypes::BitRateRole:
529 result = i18nc("Bit rate label for track metadata view", "Bit Rate");
530 break;
531 case DataTypes::SampleRateRole:
532 result = i18nc("Sample Rate label for track metadata view", "Sample Rate");
533 break;
534 case DataTypes::LastPlayDate:
535 result = i18nc("Last play date label for track metadata view", "Last played");
536 break;
537 case DataTypes::FirstPlayDate:
538 result = i18nc("First play date label for track metadata view", "First played");
539 break;
540 case DataTypes::PlayCounter:
541 result = i18nc("Play counter label for track metadata view", "Play count");
542 break;
543 case DataTypes::LyricsRole:
544 result = i18nc("Lyrics label for track metadata view", "Lyrics");
545 break;
546 case DataTypes::ResourceRole:
547 result = i18nc("Radio HTTP address for radio metadata view", "Stream Http Address");
548 break;
549 case DataTypes::ImageUrlRole:
550 result = i18nc("Image address for radio metadata view", "Image Address");
551 break;
552 case DataTypes::SecondaryTextRole:
553 case DataTypes::ShadowForImageRole:
554 case DataTypes::ChildModelRole:
555 case DataTypes::StringDurationRole:
556 case DataTypes::IsValidAlbumArtistRole:
557 case DataTypes::AllArtistsRole:
558 case DataTypes::HighestTrackRating:
559 case DataTypes::IdRole:
560 case DataTypes::ParentIdRole:
561 case DataTypes::DatabaseIdRole:
562 case DataTypes::IsSingleDiscAlbumRole:
563 case DataTypes::ContainerDataRole:
564 case DataTypes::IsPartialDataRole:
565 case DataTypes::AlbumIdRole:
566 case DataTypes::HasEmbeddedCover:
567 case DataTypes::FileModificationTime:
568 case DataTypes::PlayFrequency:
569 case DataTypes::ElementTypeRole:
570 case DataTypes::FullDataRole:
571 case DataTypes::IsDirectoryRole:
572 case DataTypes::IsPlayListRole:
573 case DataTypes::FilePathRole:
574 break;
575 }
576 return result;
577 }
578
metadataExists(DataTypes::ColumnsRoles metadataRole) const579 bool TrackMetadataModel::metadataExists(DataTypes::ColumnsRoles metadataRole) const
580 {
581 return std::find(mTrackKeys.begin(), mTrackKeys.end(), metadataRole) != mTrackKeys.end();
582 }
583
fetchLyrics()584 void TrackMetadataModel::fetchLyrics()
585 {
586 auto fileUrl = mFullData[DataTypes::ResourceRole].toUrl();
587 auto lyricicsValue = QtConcurrent::run(QThreadPool::globalInstance(), [fileUrl, this]() {
588 auto locker = QMutexLocker(&mFileScannerMutex);
589 auto trackData = mFileScanner.scanOneFile(fileUrl);
590 if (!trackData.lyrics().isEmpty()) {
591 return trackData.lyrics();
592 }
593 return QString{};
594 });
595
596 mLyricsValueWatcher.setFuture(lyricicsValue);
597 }
598
initializeForNewRadio()599 void TrackMetadataModel::initializeForNewRadio()
600 {
601 mFullData.clear();
602 mTrackData.clear();
603
604 fillDataForNewRadio();
605 }
606
fillDataForNewRadio()607 void TrackMetadataModel::fillDataForNewRadio()
608 {
609 beginResetModel();
610 mFullData.clear();
611 mTrackData.clear();
612 mTrackKeys.clear();
613
614 auto allRoles = {DataTypes::TitleRole, DataTypes::ResourceRole,
615 DataTypes::CommentRole, DataTypes::ImageUrlRole};
616
617 for (auto role : allRoles) {
618 mTrackKeys.push_back(role);
619 if (role == DataTypes::DatabaseIdRole) {
620 mFullData[role] = -1;
621 } else {
622 mFullData[role] = QString();
623 }
624 }
625 mTrackData = mFullData;
626 mFullData[DataTypes::ElementTypeRole] = ElisaUtils::Radio;
627 filterDataFromTrackData();
628 endResetModel();
629 }
630
initializeByUrl(ElisaUtils::PlayListEntryType type,const QUrl & url)631 void TrackMetadataModel::initializeByUrl(ElisaUtils::PlayListEntryType type, const QUrl &url)
632 {
633 mFullData.clear();
634 mTrackData.clear();
635 mCoverImage.clear();
636 mFileUrl.clear();
637
638 Q_EMIT lyricsChanged();
639
640 mFileUrl = url;
641
642 Q_EMIT needDataByUrl(type, url);
643 }
644
setManager(MusicListenersManager * newManager)645 void TrackMetadataModel::setManager(MusicListenersManager *newManager)
646 {
647 initialize(newManager, nullptr);
648 }
649
setDatabase(DatabaseInterface * trackDatabase)650 void TrackMetadataModel::setDatabase(DatabaseInterface *trackDatabase)
651 {
652 initialize(nullptr, trackDatabase);
653 }
654
radioData(const TrackDataType & radiosData)655 void TrackMetadataModel::radioData(const TrackDataType &radiosData)
656 {
657 if (!mFullData.isEmpty() && mFullData[DataTypes::DatabaseIdRole].toInt() != -1 &&
658 mFullData.databaseId() != radiosData.databaseId()) {
659 return;
660 }
661
662 const QList<DataTypes::ColumnsRoles> fieldsForTrack({DataTypes::TitleRole, DataTypes::ResourceRole,
663 DataTypes::CommentRole, DataTypes::ImageUrlRole});
664
665 fillDataFromTrackData(radiosData, fieldsForTrack);
666 }
667
668 #include "moc_trackmetadatamodel.cpp"
669