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