1 /* MetaDataInfo.cpp */
2 
3 /* Copyright (C) 2011-2020 Michael Lugmair (Lucio Carreras)
4  *
5  * This file is part of sayonara player
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11 
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16 
17  * You should have received a copy of the GNU General Public License
18  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "MetaDataInfo.h"
22 
23 #include "Utils/Set.h"
24 #include "Utils/Utils.h"
25 #include "Utils/Algorithm.h"
26 #include "Utils/FileUtils.h"
27 #include "Utils/Language/Language.h"
28 #include "Utils/Language/LanguageUtils.h"
29 #include "Utils/MetaData/MetaDataList.h"
30 #include "Utils/MetaData/Album.h"
31 #include "Utils/MetaData/Genre.h"
32 #include "Utils/Settings/Settings.h"
33 
34 #include "Components/Covers/CoverLocation.h"
35 
36 #include <limits>
37 #include <QStringList>
38 #include <QDateTime>
39 #include <QLocale>
40 
41 namespace Algorithm = Util::Algorithm;
42 
43 struct MetaDataInfo::Private
44 {
45 	Util::Set<QString> albums;
46 	Util::Set<QString> artists;
47 	Util::Set<QString> album_artists;
48 
49 	Util::Set<AlbumId> albumIds;
50 	Util::Set<ArtistId> artistIds;
51 	Util::Set<ArtistId> album_artistIds;
52 
53 	Util::Set<QString> paths;
54 
55 	Cover::Location coverLocation;
56 };
57 
58 MetaDataInfo::MetaDataInfo(const MetaDataList& tracks) :
59 	QObject(nullptr)
60 {
61 	m = Pimpl::make<Private>();
62 
63 	if(tracks.isEmpty())
64 	{
65 		return;
66 	}
67 
68 	MilliSeconds length = 0;
69 	Filesize filesize = 0;
70 	Year minYear = std::numeric_limits<uint16_t>::max();
71 	Year maxYear = 0;
72 	Bitrate minBitrate = std::numeric_limits<Bitrate>::max();
73 	Bitrate maxBitrate = 0;
74 	TrackNum tracknum = 0;
75 	uint64_t minCreateDate = std::numeric_limits<uint64_t>::max();
76 	uint64_t maxCreateDate = 0;
77 	uint64_t minModifyDate = minCreateDate;
78 	uint64_t maxModifyDate = 0;
79 
80 	Util::Set<QString> filetypes;
81 	Util::Set<Genre> genres;
82 	Util::Set<QString> comments;
83 
84 	for(const auto& track : tracks)
85 	{
86 		m->artists.insert(track.artist());
87 		m->albums.insert(track.album());
88 		m->album_artists.insert(track.albumArtist());
89 
90 		m->albumIds.insert(track.albumId());
91 		m->artistIds.insert(track.artistId());
92 		m->album_artistIds.insert(track.albumArtistId());
93 
94 		length += track.durationMs();
95 		filesize += track.filesize();
96 
97 		if(tracks.size() == 1)
98 		{
99 			tracknum = track.trackNumber();
100 		}
101 
102 		// bitrate
103 		if(track.bitrate() != 0)
104 		{
105 			minBitrate = std::min(track.bitrate(), minBitrate);
106 			maxBitrate = std::max(track.bitrate(), maxBitrate);
107 		}
108 
109 		// year
110 		if(track.year() != 0)
111 		{
112 			minYear = std::min(minYear, track.year());
113 			maxYear = std::max(maxYear, track.year());
114 		}
115 
116 		if(track.createdDate() != 0)
117 		{
118 			minCreateDate = std::min(minCreateDate, track.createdDate());
119 			maxCreateDate = std::max(maxCreateDate, track.createdDate());
120 		}
121 
122 		if(track.modifiedDate() != 0)
123 		{
124 			minModifyDate = std::min(minModifyDate, track.modifiedDate());
125 			maxModifyDate = std::max(maxModifyDate, track.modifiedDate());
126 		}
127 
128 		if(!track.comment().isEmpty())
129 		{
130 			comments << track.comment();
131 		}
132 
133 		// custom fields
134 		const auto& customFields = track.customFields();
135 
136 		for(const auto& field : customFields)
137 		{
138 			const auto name = field.displayName();
139 			const auto value = field.value();
140 			if(!value.isEmpty())
141 			{
142 				mAdditionalInfo << StringPair(name, value);
143 			}
144 		}
145 
146 		// genre
147 		genres << track.genres();
148 
149 		const auto path = Util::File::isWWW(track.filepath())
150 			? track.filepath()
151 			: Util::File::getParentDirectory(track.filepath());
152 
153 		m->paths << path;
154 
155 		filetypes << Util::File::getFileExtension(track.filepath());
156 	}
157 
158 	if(maxBitrate > 0)
159 	{
160 		insertIntervalInfoField(InfoStrings::Bitrate, minBitrate / 1000, maxBitrate / 1000);
161 	}
162 
163 	if(maxYear > 0)
164 	{
165 		insertIntervalInfoField(InfoStrings::Year, minYear, maxYear);
166 	}
167 
168 	insertNumericInfoField(InfoStrings::nTracks, tracks.count());
169 	insertFilesize(filesize);
170 	insertFiletype(filetypes);
171 	insertPlayingTime(length);
172 	insertGenre(genres);
173 	insertComment(comments);
174 	insertCreatedates(minCreateDate, maxCreateDate);
175 	insertModifydates(minModifyDate, maxModifyDate);
176 
177 	calcHeader(tracks);
178 	calcSubheader(tracknum);
179 	calcCoverLocation(tracks);
180 }
181 
182 MetaDataInfo::~MetaDataInfo() {}
183 
184 void MetaDataInfo::calcHeader() {}
185 
186 void MetaDataInfo::calcHeader(const MetaDataList& tracks)
187 {
188 	mHeader = (tracks.size() == 1)
189 		? tracks.first().title()
190 		: Lang::get(Lang::VariousTracks);
191 }
192 
193 void MetaDataInfo::calcSubheader() {}
194 
195 void MetaDataInfo::calcSubheader(uint16_t tracknum)
196 {
197 	mSubheader = calcArtistString();
198 
199 	if(tracknum)
200 	{
201 		mSubheader += CAR_RET + calcTracknumString(tracknum) + " " +
202 		              Lang::get(Lang::TrackOn) + " ";
203 	}
204 
205 	else
206 	{
207 		mSubheader += CAR_RET + Lang::get(Lang::On) + " ";
208 	}
209 
210 	mSubheader += calcAlbumString();
211 }
212 
213 void MetaDataInfo::calcCoverLocation() {}
214 
215 void MetaDataInfo::calcCoverLocation(const MetaDataList& tracks)
216 {
217 	if(tracks.isEmpty())
218 	{
219 		m->coverLocation = Cover::Location::invalidLocation();
220 		return;
221 	}
222 
223 	if(tracks.size() == 1)
224 	{
225 		m->coverLocation = Cover::Location::coverLocation(tracks[0]);
226 	}
227 
228 	else if(albumIds().size() == 1)
229 	{
230 		Album album;
231 
232 		album.setId(albumIds().first());
233 		album.setName(m->albums.first());
234 		album.setArtists(m->artists.toList());
235 
236 		if(m->album_artists.size() > 0)
237 		{
238 			album.setAlbumArtist(m->album_artists.first());
239 		}
240 
241 		album.setDatabaseId(tracks[0].databaseId());
242 
243 		QStringList pathHints;
244 		Util::Algorithm::transform(tracks, pathHints, [](const auto& track){
245 			return track.filepath();
246 		});
247 
248 		album.setPathHint(pathHints);
249 
250 		m->coverLocation = Cover::Location::coverLocation(album);
251 	}
252 
253 	else if(m->albums.size() == 1 && m->artists.size() == 1)
254 	{
255 		const auto album = m->albums.first();
256 		const auto artist = m->artists.first();
257 
258 		m->coverLocation = Cover::Location::coverLocation(album, artist);
259 	}
260 
261 	else if(m->albums.size() == 1 && m->album_artists.size() == 1)
262 	{
263 		const auto album = m->albums.first();
264 		const auto artist = m->album_artists.first();
265 
266 		m->coverLocation = Cover::Location::coverLocation(album, artist);
267 	}
268 
269 	else if(m->albums.size() == 1)
270 	{
271 		QString album = m->albums.first();
272 		m->coverLocation = Cover::Location::coverLocation(album, m->artists.toList());
273 	}
274 
275 	else
276 	{
277 		m->coverLocation = Cover::Location::invalidLocation();
278 	}
279 }
280 
281 QString MetaDataInfo::calcArtistString() const
282 {
283 	if(m->album_artists.size() == 1)
284 	{
285 		return m->album_artists.first();
286 	}
287 
288 	if(m->artists.size() == 1)
289 	{
290 		return m->artists.first();
291 	}
292 
293 	return QString::number(m->artists.size()) + " " + Lang::get(Lang::VariousArtists);
294 }
295 
296 QString MetaDataInfo::calcAlbumString()
297 {
298 	return (m->albums.size() == 1)
299 	       ? m->albums.first()
300 	       : QString::number(m->artists.size()) + " " + Lang::get(Lang::VariousAlbums);
301 }
302 
303 QString MetaDataInfo::calcTracknumString(uint16_t tracknum)
304 {
305 	QString str;
306 	switch(tracknum)
307 	{
308 		case 1:
309 			str = Lang::get(Lang::First);
310 			break;
311 		case 2:
312 			str = Lang::get(Lang::Second);
313 			break;
314 		case 3:
315 			str = Lang::get(Lang::Third);
316 			break;
317 		default:
318 			str = QString::number(tracknum) + Lang::get(Lang::Th);
319 			break;
320 	}
321 
322 	return str;
323 }
324 
325 void MetaDataInfo::insertPlayingTime(MilliSeconds ms)
326 {
327 	const auto timeString = Util::msToString(ms, "$De $He $M:$S");
328 	mInfo.insert(InfoStrings::PlayingTime, timeString);
329 }
330 
331 void MetaDataInfo::insertGenre(const Util::Set<Genre>& genres)
332 {
333 	if(genres.isEmpty())
334 	{
335 		return;
336 	}
337 
338 	QStringList genreList;
339 	Util::Algorithm::transform(genres, genreList, [](const auto& genre) {
340 		return genre.name();
341 	});
342 
343 	const auto genreString = genreList.join(", ");
344 	auto oldGenre = mInfo[InfoStrings::Genre];
345 	if(!oldGenre.isEmpty())
346 	{
347 		oldGenre += ", ";
348 	}
349 
350 	mInfo[InfoStrings::Genre] = oldGenre + genreString;
351 }
352 
353 void MetaDataInfo::insertFilesize(uint64_t filesize)
354 {
355 	const auto filesizeString = Util::File::getFilesizeString(filesize);
356 	mInfo.insert(InfoStrings::Filesize, filesizeString);
357 }
358 
359 void MetaDataInfo::insertComment(const Util::Set<QString>& comments)
360 {
361 	auto commentsString = QStringList(comments.toList()).join(", ");
362 	if(commentsString.size() > 50)
363 	{
364 		commentsString = commentsString.left(50) + "...";
365 	}
366 
367 	mInfo.insert(InfoStrings::Comment, commentsString);
368 }
369 
370 static QString get_date_text(uint64_t minDate, uint64_t maxDate)
371 {
372 	const auto minDateTime = Util::intToDate(minDate);
373 	const auto maxDateTime = Util::intToDate(maxDate);
374 
375 	const auto locale = Util::Language::getCurrentLocale();
376 
377 	auto text = minDateTime.toString(locale.dateTimeFormat(QLocale::ShortFormat));
378 	if(minDate != maxDate)
379 	{
380 		text += " -\n" + maxDateTime.toString(locale.dateTimeFormat(QLocale::ShortFormat));
381 	}
382 
383 	return text;
384 }
385 
386 void MetaDataInfo::insertCreatedates(uint64_t min_date, uint64_t max_date)
387 {
388 	mInfo.insert(InfoStrings::CreateDate, get_date_text(min_date, max_date));
389 }
390 
391 void MetaDataInfo::insertModifydates(uint64_t min_date, uint64_t max_date)
392 {
393 	mInfo.insert(InfoStrings::ModifyDate, get_date_text(min_date, max_date));
394 }
395 
396 void MetaDataInfo::insertFiletype(const Util::Set<QString>& filetypes)
397 {
398 	QStringList filetypes_str(filetypes.toList());
399 	mInfo.insert(InfoStrings::Filetype, filetypes_str.join(", "));
400 }
401 
402 QString MetaDataInfo::header() const
403 {
404 	return mHeader;
405 }
406 
407 QString MetaDataInfo::subheader() const
408 {
409 	return mSubheader;
410 }
411 
412 QString MetaDataInfo::getInfoString(InfoStrings idx) const
413 {
414 	switch(idx)
415 	{
416 		case InfoStrings::nTracks:
417 			return QString("#") + Lang::get(Lang::Tracks);
418 		case InfoStrings::nAlbums:
419 			return QString("#") + Lang::get(Lang::Albums);
420 		case InfoStrings::nArtists:
421 			return QString("#") + Lang::get(Lang::Artists);
422 		case InfoStrings::Filesize:
423 			return Lang::get(Lang::Filesize);
424 		case InfoStrings::PlayingTime:
425 			return Lang::get(Lang::PlayingTime);
426 		case InfoStrings::Year:
427 			return Lang::get(Lang::Year);
428 		case InfoStrings::Sampler:
429 			return Lang::get(Lang::Sampler);
430 		case InfoStrings::Bitrate:
431 			return Lang::get(Lang::Bitrate);
432 		case InfoStrings::Genre:
433 			return Lang::get(Lang::Genre);
434 		case InfoStrings::Filetype:
435 			return Lang::get(Lang::Filetype);
436 		case InfoStrings::Comment:
437 			return Lang::get(Lang::Comment);
438 		case InfoStrings::CreateDate:
439 			return Lang::get(Lang::Created);
440 		case InfoStrings::ModifyDate:
441 			return Lang::get(Lang::Modified);
442 
443 		default:
444 			break;
445 	}
446 
447 	return "";
448 }
449 
450 QString MetaDataInfo::infostring() const
451 {
452 	QString str;
453 
454 	for(auto it = mInfo.cbegin(); it != mInfo.cend(); it++)
455 	{
456 		str += BOLD(getInfoString(it.key())) + it.value() + CAR_RET;
457 	}
458 
459 	return str;
460 }
461 
462 QList<StringPair> MetaDataInfo::infostringMap() const
463 {
464 	QList<StringPair> ret;
465 
466 	for(auto it = mInfo.cbegin(); it != mInfo.cend(); it++)
467 	{
468 		const auto value = (it.value().isEmpty()) ? "-" : it.value();
469 		ret << StringPair(getInfoString(it.key()), value);
470 	}
471 
472 	if(!mAdditionalInfo.isEmpty())
473 	{
474 		ret << StringPair(QString(), QString());
475 		ret.append(mAdditionalInfo);
476 	}
477 
478 	return ret;
479 }
480 
481 QStringList MetaDataInfo::paths() const
482 {
483 	QStringList links;
484 
485 	const auto dark = (GetSetting(Set::Player_Style) == 1);
486 	Util::Algorithm::transform(m->paths, links, [&](const auto& path){
487 		return Util::createLink(path, dark, false, path);
488 	});
489 
490 	return links;
491 }
492 
493 Cover::Location MetaDataInfo::coverLocation() const
494 {
495 	return m->coverLocation;
496 }
497 
498 const Util::Set<QString>& MetaDataInfo::albums() const
499 {
500 	return m->albums;
501 }
502 
503 const Util::Set<QString>& MetaDataInfo::artists() const
504 {
505 	return m->artists;
506 }
507 
508 const Util::Set<QString>& MetaDataInfo::albumArtists() const
509 {
510 	return m->album_artists;
511 }
512 
513 const Util::Set<AlbumId>& MetaDataInfo::albumIds() const
514 {
515 	return m->albumIds;
516 }
517 
518 const Util::Set<ArtistId>& MetaDataInfo::artistIds() const
519 {
520 	return m->artistIds;
521 }
522 
523 const Util::Set<ArtistId>& MetaDataInfo::albumArtistIds() const
524 {
525 	return m->album_artistIds;
526 }
527 
528 void MetaDataInfo::insertIntervalInfoField(InfoStrings key, int min, int max)
529 {
530 	QString str;
531 
532 	if(min == max)
533 	{
534 		str = QString::number(min);
535 	}
536 
537 	else
538 	{
539 		str = QString::number(min) + " - " + QString::number(max);
540 	}
541 
542 	if(key == InfoStrings::Bitrate)
543 	{
544 		str += " kBit/s";
545 	}
546 
547 	mInfo.insert(key, str);
548 }
549 
550 void MetaDataInfo::insertNumericInfoField(InfoStrings key, int number)
551 {
552 	QString str = QString::number(number);
553 
554 	mInfo.insert(key, str);
555 }
556