MythTV  master
musicmetadata.cpp
Go to the documentation of this file.
1 
2 #include "musicmetadata.h"
3 
4 // qt
5 #include <QApplication>
6 #include <QDateTime>
7 #include <QDir>
8 #include <QDomDocument>
9 #include <QRegExp>
10 #include <QScopedPointer>
11 #include <utility>
12 
13 // mythtv
14 #include "mythcontext.h"
15 #include "mythdate.h"
16 #include "mythdb.h"
17 #include "mythdirs.h"
18 #include "mythdownloadmanager.h"
19 #include "mythlogging.h"
20 #include "mythdate.h"
21 #include "remotefile.h"
22 #include "storagegroup.h"
23 #include "mythsystem.h"
24 #include "mythcoreutil.h"
25 
26 // mythbase
27 #include "mythsorthelper.h"
28 
29 // libmythui
30 #include "mythprogressdialog.h"
31 #include "mythmainwindow.h"
32 
33 // libmythmetadata
34 #include "metaio.h"
35 #include "metaioid3.h"
36 #include "metaiomp4.h"
37 #include "metaioavfcomment.h"
38 #include "metaiooggvorbis.h"
39 #include "metaioflacvorbis.h"
40 #include "metaiowavpack.h"
41 #include "musicutils.h"
42 #include "lyricsdata.h"
43 
44 using namespace std;
45 
46 static QString thePrefix = "the ";
47 
49 {
50  return a.Filename() == b.Filename();
51 }
52 
54 {
55  return a.Filename() != b.Filename();
56 }
57 
58 // this ctor is for radio streams
59 MusicMetadata::MusicMetadata(int lid, QString lbroadcaster, QString lchannel, QString ldescription,
60  UrlList lurls, QString llogourl, QString lgenre, QString lmetaformat,
61  QString lcountry, QString llanguage, QString lformat)
62  : m_genre(std::move(lgenre)),
63  m_format(std::move(lformat)),
64  m_id(lid),
65  m_filename(lurls[0]),
66  m_broadcaster(std::move(lbroadcaster)),
67  m_channel(std::move(lchannel)),
68  m_description(std::move(ldescription)),
69  m_logoUrl(std::move(llogourl)),
70  m_metaFormat(std::move(lmetaformat)),
71  m_country(std::move(lcountry)),
72  m_language(std::move(llanguage))
73 {
74  for (int x = 0; x < STREAMURLCOUNT; x++)
75  m_urls[x] = lurls[x];
76 
79 }
80 
82 {
83  if (m_albumArt)
84  {
85  delete m_albumArt;
86  m_albumArt = nullptr;
87  }
88 
89  if (m_lyricsData)
90  {
91  delete m_lyricsData;
92  m_lyricsData = nullptr;
93  }
94 }
95 
96 
98 {
99  if (this == &rhs)
100  return *this;
101 
102  m_artist = rhs.m_artist;
106  m_album = rhs.m_album;
107  m_albumSort = rhs.m_albumSort;
108  m_title = rhs.m_title;
109  m_titleSort = rhs.m_titleSort;
112  m_genre = rhs.m_genre;
113  m_year = rhs.m_year;
114  m_trackNum = rhs.m_trackNum;
116  m_discNum = rhs.m_discNum;
117  m_discCount = rhs.m_discCount;
118  m_length = rhs.m_length;
119  m_rating = rhs.m_rating;
120  m_lastPlay = rhs.m_lastPlay;
122  m_dateAdded = rhs.m_dateAdded;
123  m_playCount = rhs.m_playCount;
126  m_id = rhs.m_id;
127  m_filename = rhs.m_filename;
129  m_hostname = rhs.m_hostname;
131  m_artistId = rhs.m_artistId;
133  m_albumId = rhs.m_albumId;
134  m_genreId = rhs.m_genreId;
135  m_albumArt = nullptr;
136  m_lyricsData = nullptr;
137  m_format = rhs.m_format;
138  m_changed = rhs.m_changed;
139  m_fileSize = rhs.m_fileSize;
141  m_channel = rhs.m_channel;
143 
144  for (int x = 0; x < 5; x++)
145  m_urls[x] = rhs.m_urls[x];
146  m_logoUrl = rhs.m_logoUrl;
148  m_country = rhs.m_country;
149  m_language = rhs.m_language;
150 
151  return *this;
152 }
153 
154 // return true if this == mdata
156 {
157  return (
158  m_artist == mdata->m_artist &&
160  m_album == mdata->m_album &&
161  m_title == mdata->m_title &&
162  m_year == mdata->m_year &&
163  m_trackNum == mdata->m_trackNum &&
164  m_trackCount == mdata->m_trackCount &&
165  m_discNum == mdata->m_discNum &&
166  m_discCount == mdata->m_discCount &&
167  //m_length == mdata->m_length &&
168  m_rating == mdata->m_rating &&
169  m_lastPlay == mdata->m_lastPlay &&
170  m_playCount == mdata->m_playCount &&
171  m_compilation == mdata->m_compilation &&
172  m_filename == mdata->m_filename &&
173  m_directoryId == mdata->m_directoryId &&
174  m_artistId == mdata->m_artistId &&
175  m_compartistId == mdata->m_compartistId &&
176  m_albumId == mdata->m_albumId &&
177  m_genreId == mdata->m_genreId &&
178  m_format == mdata->m_format &&
179  m_fileSize == mdata->m_fileSize
180  );
181 }
182 
184 {
185  if (m_id < 1)
186  return;
187 
188  if (m_tempLastPlay.isValid())
189  {
192 
193  m_tempLastPlay = QDateTime();
194  }
195 
196  MSqlQuery query(MSqlQuery::InitCon());
197  query.prepare("UPDATE music_songs set rating = :RATING , "
198  "numplays = :PLAYCOUNT , lastplay = :LASTPLAY "
199  "where song_id = :ID ;");
200  query.bindValue(":RATING", m_rating);
201  query.bindValue(":PLAYCOUNT", m_playCount);
202  query.bindValue(":LASTPLAY", m_lastPlay);
203  query.bindValue(":ID", m_id);
204 
205  if (!query.exec())
206  MythDB::DBError("music persist", query);
207 
208  m_changed = false;
209 }
210 
212 {
213  if (m_id < 1)
214  return;
215 
216  MSqlQuery query(MSqlQuery::InitCon());
217  query.prepare("UPDATE music_songs SET hostname = :HOSTNAME "
218  "WHERE song_id = :ID ;");
219  query.bindValue(":HOSTNAME", m_hostname);
220  query.bindValue(":ID", m_id);
221 
222  if (!query.exec())
223  MythDB::DBError("music save hostname", query);
224 }
225 
226 // static
228 {
229  // find the trackid for this filename
230  QString sqldir = filename.section('/', 0, -2);
231 
232  QString sqlfilename = filename.section('/', -1);
233 
234  MSqlQuery query(MSqlQuery::InitCon());
235  query.prepare(
236  "SELECT song_id FROM music_songs "
237  "LEFT JOIN music_directories ON music_songs.directory_id=music_directories.directory_id "
238  "WHERE music_songs.filename = :FILENAME "
239  "AND music_directories.path = :DIRECTORY ;");
240  query.bindValue(":FILENAME", sqlfilename);
241  query.bindValue(":DIRECTORY", sqldir);
242 
243  if (!query.exec())
244  {
245  MythDB::DBError("MusicMetadata::createFromFilename", query);
246  return nullptr;
247  }
248 
249  if (!query.next())
250  {
251  LOG(VB_GENERAL, LOG_WARNING,
252  QString("MusicMetadata::createFromFilename: Could not find '%1'")
253  .arg(filename));
254  return nullptr;
255  }
256 
257  int songID = query.value(0).toInt();
258 
259  return MusicMetadata::createFromID(songID);
260 }
261 
262 // static
264 {
265  MSqlQuery query(MSqlQuery::InitCon());
266  query.prepare("SELECT music_artists.artist_name, "
267  "music_comp_artists.artist_name AS compilation_artist, "
268  "music_albums.album_name, music_songs.name, music_genres.genre, "
269  "music_songs.year, music_songs.track, music_songs.length, "
270  "music_songs.song_id, music_songs.rating, music_songs.numplays, "
271  "music_songs.lastplay, music_albums.compilation, music_songs.format, "
272  "music_songs.track_count, music_songs.size, music_songs.date_entered, "
273  "music_songs.disc_number, music_songs.disc_count, "
274  "CONCAT_WS('/', music_directories.path, music_songs.filename) AS filename, "
275  "music_songs.hostname "
276  "FROM music_songs "
277  "LEFT JOIN music_directories ON music_songs.directory_id=music_directories.directory_id "
278  "LEFT JOIN music_artists ON music_songs.artist_id=music_artists.artist_id "
279  "LEFT JOIN music_albums ON music_songs.album_id=music_albums.album_id "
280  "LEFT JOIN music_artists AS music_comp_artists ON music_albums.artist_id=music_comp_artists.artist_id "
281  "LEFT JOIN music_genres ON music_songs.genre_id=music_genres.genre_id "
282  "WHERE music_songs.song_id = :SONGID; ");
283  query.bindValue(":SONGID", trackid);
284 
285  if (query.exec() && query.next())
286  {
287  auto *mdata = new MusicMetadata();
288  mdata->m_artist = query.value(0).toString();
289  mdata->m_compilationArtist = query.value(1).toString();
290  mdata->m_album = query.value(2).toString();
291  mdata->m_title = query.value(3).toString();
292  mdata->m_genre = query.value(4).toString();
293  mdata->m_year = query.value(5).toInt();
294  mdata->m_trackNum = query.value(6).toInt();
295  mdata->m_length = query.value(7).toInt();
296  mdata->m_id = query.value(8).toUInt();
297  mdata->m_rating = query.value(9).toInt();
298  mdata->m_playCount = query.value(10).toInt();
299  mdata->m_lastPlay = query.value(11).toDateTime();
300  mdata->m_compilation = (query.value(12).toInt() > 0);
301  mdata->m_format = query.value(13).toString();
302  mdata->m_trackCount = query.value(14).toInt();
303  mdata->m_fileSize = query.value(15).toULongLong();
304  mdata->m_dateAdded = query.value(16).toDateTime();
305  mdata->m_discNum = query.value(17).toInt();
306  mdata->m_discCount = query.value(18).toInt();
307  mdata->m_filename = query.value(19).toString();
308  mdata->m_hostname = query.value(20).toString();
309  mdata->ensureSortFields();
310 
311  if (!QHostAddress(mdata->m_hostname).isNull()) // A bug caused an IP to replace hostname, reset and it will fix itself
312  {
313  mdata->m_hostname = "";
314  mdata->saveHostname();
315  }
316 
317  return mdata;
318  }
319 
320  return nullptr;
321 }
322 
323 // static
325 {
326  // we are only interested in the global setting so remove any local host setting just in case
327  GetMythDB()->ClearSetting("MusicStreamListModified");
328 
329  // make sure we are not already doing an update
330  if (gCoreContext->GetSetting("MusicStreamListModified") == "Updating")
331  {
332  LOG(VB_GENERAL, LOG_ERR, "MusicMetadata: looks like we are already updating the radio streams list");
333  return false;
334  }
335 
336  QByteArray compressedData;
337  QByteArray uncompressedData;
338 
339  // check if the streamlist has been updated since we last checked
340  QDateTime lastModified = GetMythDownloadManager()->GetLastModified(QString(STREAMUPDATEURL));
341 
342  QDateTime lastUpdate = QDateTime::fromString(gCoreContext->GetSetting("MusicStreamListModified"), Qt::ISODate);
343 
344  if (lastModified <= lastUpdate)
345  {
346  LOG(VB_GENERAL, LOG_INFO, "MusicMetadata: radio streams list is already up to date");
347  return true;
348  }
349 
350  gCoreContext->SaveSettingOnHost("MusicStreamListModified", "Updating", nullptr);
351 
352  LOG(VB_GENERAL, LOG_INFO, "MusicMetadata: downloading radio streams list");
353 
354  // download compressed stream file
355  if (!GetMythDownloadManager()->download(QString(STREAMUPDATEURL), &compressedData), false)
356  {
357  LOG(VB_GENERAL, LOG_ERR, "MusicMetadata: failed to download radio stream list");
358  gCoreContext->SaveSettingOnHost("MusicStreamListModified", "", nullptr);
359  return false;
360  }
361 
362  // uncompress the data
363  uncompressedData = gzipUncompress(compressedData);
364 
365  QString errorMsg;
366  int errorLine = 0;
367  int errorColumn = 0;
368 
369  // load the xml
370  QDomDocument domDoc;
371 
372  if (!domDoc.setContent(uncompressedData, false, &errorMsg,
373  &errorLine, &errorColumn))
374  {
375  LOG(VB_GENERAL, LOG_ERR,
376  "MusicMetadata: Could not read content of streams.xml" +
377  QString("\n\t\t\tError parsing %1").arg(STREAMUPDATEURL) +
378  QString("\n\t\t\tat line: %1 column: %2 msg: %3")
379  .arg(errorLine).arg(errorColumn).arg(errorMsg));
380  gCoreContext->SaveSettingOnHost("MusicStreamListModified", "", nullptr);
381  return false;
382  }
383 
384  MSqlQuery query(MSqlQuery::InitCon());
385  query.prepare("DELETE FROM music_streams;");
386  if (!query.exec() || !query.isActive() || query.numRowsAffected() < 0)
387  {
388  MythDB::DBError("music delete radio streams", query);
389  gCoreContext->SaveSettingOnHost("MusicStreamListModified", "", nullptr);
390  return false;
391  }
392 
393  LOG(VB_GENERAL, LOG_INFO, "MusicMetadata: processing radio streams list");
394 
395  QDomNodeList itemList = domDoc.elementsByTagName("item");
396 
397  QDomNode itemNode;
398  for (int i = 0; i < itemList.count(); i++)
399  {
400  itemNode = itemList.item(i);
401 
402  query.prepare("INSERT INTO music_streams (broadcaster, channel, description, url1, url2, url3, url4, url5,"
403  " logourl, genre, metaformat, country, language) "
404  "VALUES (:BROADCASTER, :CHANNEL, :DESC, :URL1, :URL2, :URL3, :URL4, :URL5,"
405  " :LOGOURL, :GENRE, :META, :COUNTRY, :LANG);");
406 
407  query.bindValue(":BROADCASTER", itemNode.namedItem(QString("broadcaster")).toElement().text());
408  query.bindValue(":CHANNEL", itemNode.namedItem(QString("channel")).toElement().text());
409  query.bindValue(":DESC", itemNode.namedItem(QString("description")).toElement().text());
410  query.bindValue(":URL1", itemNode.namedItem(QString("url1")).toElement().text());
411  query.bindValue(":URL2", itemNode.namedItem(QString("url2")).toElement().text());
412  query.bindValue(":URL3", itemNode.namedItem(QString("url3")).toElement().text());
413  query.bindValue(":URL4", itemNode.namedItem(QString("url4")).toElement().text());
414  query.bindValue(":URL5", itemNode.namedItem(QString("url5")).toElement().text());
415  query.bindValue(":LOGOURL", itemNode.namedItem(QString("logourl")).toElement().text());
416  query.bindValue(":GENRE", itemNode.namedItem(QString("genre")).toElement().text());
417  query.bindValue(":META", itemNode.namedItem(QString("metadataformat")).toElement().text());
418  query.bindValue(":COUNTRY", itemNode.namedItem(QString("country")).toElement().text());
419  query.bindValue(":LANG", itemNode.namedItem(QString("language")).toElement().text());
420 
421  if (!query.exec() || !query.isActive() || query.numRowsAffected() <= 0)
422  {
423  MythDB::DBError("music insert radio stream", query);
424  gCoreContext->SaveSettingOnHost("MusicStreamListModified", "", nullptr);
425  return false;
426  }
427  }
428 
429  gCoreContext->SaveSettingOnHost("MusicStreamListModified", lastModified.toString(Qt::ISODate), nullptr);
430 
431  LOG(VB_GENERAL, LOG_INFO, "MusicMetadata: updating radio streams list completed OK");
432 
433  return true;
434 }
435 
437 {
439 
440  if (!mdata)
441  {
442  LOG(VB_GENERAL, LOG_ERR, QString("MusicMetadata: Asked to reload metadata "
443  "for trackID: %1 but not found!").arg(m_id));
444 
445  return;
446  }
447 
448  *this = *mdata;
449 
450  delete mdata;
451 
452  m_directoryId = -1;
453  m_artistId = -1;
454  m_compartistId = -1;
455  m_albumId = -1;
456  m_genreId = -1;
457 }
458 
460 {
461  if (m_directoryId < 0)
462  {
463  QString sqldir = m_filename.section('/', 0, -2);
464  QString sqlfilename = m_filename.section('/', -1);
465 
467 
468  MSqlQuery query(MSqlQuery::InitCon());
469 
470  if (sqldir.isEmpty())
471  {
472  m_directoryId = 0;
473  }
474  else if (m_directoryId < 0)
475  {
476  // Load the directory id
477  query.prepare("SELECT directory_id FROM music_directories "
478  "WHERE path = :DIRECTORY ;");
479  query.bindValue(":DIRECTORY", sqldir);
480 
481  if (!query.exec() || !query.isActive())
482  {
483  MythDB::DBError("music select directory id", query);
484  return -1;
485  }
486  if (query.next())
487  {
488  m_directoryId = query.value(0).toInt();
489  }
490  else
491  {
492  query.prepare("INSERT INTO music_directories (path) VALUES (:DIRECTORY);");
493  query.bindValue(":DIRECTORY", sqldir);
494 
495  if (!query.exec() || !query.isActive() || query.numRowsAffected() <= 0)
496  {
497  MythDB::DBError("music insert directory", query);
498  return -1;
499  }
500  m_directoryId = query.lastInsertId().toInt();
501  }
502  }
503  }
504 
505  return m_directoryId;
506 }
507 
509 {
510  if (m_artistId < 0)
511  {
512  MSqlQuery query(MSqlQuery::InitCon());
513 
514  // Load the artist id
515  query.prepare("SELECT artist_id FROM music_artists "
516  "WHERE artist_name = :ARTIST ;");
517  query.bindValueNoNull(":ARTIST", m_artist);
518 
519  if (!query.exec() || !query.isActive())
520  {
521  MythDB::DBError("music select artist id", query);
522  return -1;
523  }
524  if (query.next())
525  {
526  m_artistId = query.value(0).toInt();
527  }
528  else
529  {
530  query.prepare("INSERT INTO music_artists (artist_name) VALUES (:ARTIST);");
531  query.bindValueNoNull(":ARTIST", m_artist);
532 
533  if (!query.exec() || !query.isActive() || query.numRowsAffected() <= 0)
534  {
535  MythDB::DBError("music insert artist", query);
536  return -1;
537  }
538  m_artistId = query.lastInsertId().toInt();
539  }
540 
541  // Compilation Artist
543  {
545  }
546  else
547  {
548  query.prepare("SELECT artist_id FROM music_artists "
549  "WHERE artist_name = :ARTIST ;");
550  query.bindValueNoNull(":ARTIST", m_compilationArtist);
551  if (!query.exec() || !query.isActive())
552  {
553  MythDB::DBError("music select compilation artist id", query);
554  return -1;
555  }
556  if (query.next())
557  {
558  m_compartistId = query.value(0).toInt();
559  }
560  else
561  {
562  query.prepare("INSERT INTO music_artists (artist_name) VALUES (:ARTIST);");
563  query.bindValueNoNull(":ARTIST", m_compilationArtist);
564 
565  if (!query.exec() || !query.isActive() || query.numRowsAffected() <= 0)
566  {
567  MythDB::DBError("music insert compilation artist", query);
568  return -1 ;
569  }
570  m_compartistId = query.lastInsertId().toInt();
571  }
572  }
573  }
574 
575  return m_artistId;
576 }
577 
579 {
580  if (m_albumId < 0)
581  {
582  MSqlQuery query(MSqlQuery::InitCon());
583 
584  query.prepare("SELECT album_id FROM music_albums "
585  "WHERE artist_id = :COMP_ARTIST_ID "
586  " AND album_name = :ALBUM ;");
587  query.bindValueNoNull(":COMP_ARTIST_ID", m_compartistId);
588  query.bindValueNoNull(":ALBUM", m_album);
589  if (!query.exec() || !query.isActive())
590  {
591  MythDB::DBError("music select album id", query);
592  return -1;
593  }
594  if (query.next())
595  {
596  m_albumId = query.value(0).toInt();
597  }
598  else
599  {
600  query.prepare("INSERT INTO music_albums (artist_id, album_name, compilation, year) "
601  "VALUES (:COMP_ARTIST_ID, :ALBUM, :COMPILATION, :YEAR);");
602  query.bindValueNoNull(":COMP_ARTIST_ID", m_compartistId);
603  query.bindValueNoNull(":ALBUM", m_album);
604  query.bindValue(":COMPILATION", m_compilation);
605  query.bindValue(":YEAR", m_year);
606 
607  if (!query.exec() || !query.isActive() || query.numRowsAffected() <= 0)
608  {
609  MythDB::DBError("music insert album", query);
610  return -1;
611  }
612  m_albumId = query.lastInsertId().toInt();
613  }
614  }
615 
616  return m_albumId;
617 }
618 
620 {
621  if (m_genreId < 0)
622  {
623  MSqlQuery query(MSqlQuery::InitCon());
624 
625  query.prepare("SELECT genre_id FROM music_genres "
626  "WHERE genre = :GENRE ;");
627  query.bindValueNoNull(":GENRE", m_genre);
628  if (!query.exec() || !query.isActive())
629  {
630  MythDB::DBError("music select genre id", query);
631  return -1;
632  }
633  if (query.next())
634  {
635  m_genreId = query.value(0).toInt();
636  }
637  else
638  {
639  query.prepare("INSERT INTO music_genres (genre) VALUES (:GENRE);");
640  query.bindValueNoNull(":GENRE", m_genre);
641 
642  if (!query.exec() || !query.isActive() || query.numRowsAffected() <= 0)
643  {
644  MythDB::DBError("music insert genre", query);
645  return -1;
646  }
647  m_genreId = query.lastInsertId().toInt();
648  }
649  }
650 
651  return m_genreId;
652 }
653 
654 void MusicMetadata::setUrl(const QString& url, uint index)
655 {
656  if (index < STREAMURLCOUNT)
657  m_urls[index] = url;
658 }
659 
660 QString MusicMetadata::Url(uint index)
661 {
662  if (index < STREAMURLCOUNT)
663  return m_urls[index];
664 
665  return QString();
666 }
667 
669 {
670  if (m_directoryId < 0)
671  getDirectoryId();
672 
673  if (m_artistId < 0)
674  getArtistId();
675 
676  if (m_albumId < 0)
677  getAlbumId();
678 
679  if (m_genreId < 0)
680  getGenreId();
681 
682  // We have all the id's now. We can insert it.
683  QString strQuery;
684  if (m_id < 1)
685  {
686  strQuery = "INSERT INTO music_songs ( directory_id,"
687  " artist_id, album_id, name, genre_id,"
688  " year, track, length, filename,"
689  " rating, format, date_entered, date_modified,"
690  " numplays, track_count, disc_number, disc_count,"
691  " size, hostname) "
692  "VALUES ( "
693  " :DIRECTORY, "
694  " :ARTIST, :ALBUM, :TITLE, :GENRE,"
695  " :YEAR, :TRACKNUM, :LENGTH, :FILENAME,"
696  " :RATING, :FORMAT, :DATE_ADD, :DATE_MOD,"
697  " :PLAYCOUNT,:TRACKCOUNT, :DISC_NUMBER, :DISC_COUNT,"
698  " :SIZE, :HOSTNAME );";
699  }
700  else
701  {
702  strQuery = "UPDATE music_songs SET"
703  " directory_id = :DIRECTORY"
704  ", artist_id = :ARTIST"
705  ", album_id = :ALBUM"
706  ", name = :TITLE"
707  ", genre_id = :GENRE"
708  ", year = :YEAR"
709  ", track = :TRACKNUM"
710  ", length = :LENGTH"
711  ", filename = :FILENAME"
712  ", rating = :RATING"
713  ", format = :FORMAT"
714  ", date_modified = :DATE_MOD "
715  ", numplays = :PLAYCOUNT "
716  ", track_count = :TRACKCOUNT "
717  ", disc_number = :DISC_NUMBER "
718  ", disc_count = :DISC_COUNT "
719  ", size = :SIZE "
720  ", hostname = :HOSTNAME "
721  "WHERE song_id= :ID ;";
722  }
723 
724  QString sqldir = m_filename.section('/', 0, -2);
725  QString sqlfilename = m_filename.section('/', -1);
726 
727  MSqlQuery query(MSqlQuery::InitCon());
728 
729  query.prepare(strQuery);
730 
731  query.bindValue(":DIRECTORY", m_directoryId);
732  query.bindValue(":ARTIST", m_artistId);
733  query.bindValue(":ALBUM", m_albumId);
734  query.bindValue(":TITLE", m_title);
735  query.bindValue(":GENRE", m_genreId);
736  query.bindValue(":YEAR", m_year);
737  query.bindValue(":TRACKNUM", m_trackNum);
738  query.bindValue(":LENGTH", m_length);
739  query.bindValue(":FILENAME", sqlfilename);
740  query.bindValue(":RATING", m_rating);
741  query.bindValueNoNull(":FORMAT", m_format);
742  query.bindValue(":DATE_MOD", MythDate::current());
743  query.bindValue(":PLAYCOUNT", m_playCount);
744 
745  if (m_id < 1)
746  query.bindValue(":DATE_ADD", MythDate::current());
747  else
748  query.bindValue(":ID", m_id);
749 
750  query.bindValue(":TRACKCOUNT", m_trackCount);
751  query.bindValue(":DISC_NUMBER", m_discNum);
752  query.bindValue(":DISC_COUNT",m_discCount);
753  query.bindValue(":SIZE", (quint64)m_fileSize);
754  query.bindValue(":HOSTNAME", m_hostname);
755 
756  if (!query.exec())
757  MythDB::DBError("MusicMetadata::dumpToDatabase - updating music_songs",
758  query);
759 
760  if (m_id < 1 && query.isActive() && 1 == query.numRowsAffected())
761  m_id = query.lastInsertId().toInt();
762 
763  // save the albumart to the db
764  if (m_albumArt)
766 
767  // make sure the compilation flag is updated
768  query.prepare("UPDATE music_albums SET compilation = :COMPILATION, year = :YEAR "
769  "WHERE music_albums.album_id = :ALBUMID");
770  query.bindValue(":ALBUMID", m_albumId);
771  query.bindValue(":COMPILATION", m_compilation);
772  query.bindValue(":YEAR", m_year);
773 
774  if (!query.exec() || !query.isActive())
775  {
776  MythDB::DBError("music compilation update", query);
777  return;
778  }
779 }
780 
781 // Default values for formats
782 // NB These will eventually be customizable....
783 QString MusicMetadata::s_formatNormalFileArtist = "ARTIST";
785 QString MusicMetadata::s_formatNormalCdArtist = "ARTIST";
786 QString MusicMetadata::s_formatNormalCdTrack = "TITLE";
787 QString MusicMetadata::s_formatCompilationFileArtist = "COMPARTIST";
788 QString MusicMetadata::s_formatCompilationFileTrack = "TITLE (ARTIST)";
789 QString MusicMetadata::s_formatCompilationCdArtist = "COMPARTIST";
790 QString MusicMetadata::s_formatCompilationCdTrack = "TITLE (ARTIST)";
791 
793 {
794  QString tmp;
795 
796  tmp = gCoreContext->GetSetting("MusicFormatNormalFileArtist");
797  if (!tmp.isEmpty())
799 
800  tmp = gCoreContext->GetSetting("MusicFormatNormalFileTrack");
801  if (!tmp.isEmpty())
803 
804  tmp = gCoreContext->GetSetting("MusicFormatNormalCDArtist");
805  if (!tmp.isEmpty())
807 
808  tmp = gCoreContext->GetSetting("MusicFormatNormalCDTrack");
809  if (!tmp.isEmpty())
811 
812  tmp = gCoreContext->GetSetting("MusicFormatCompilationFileArtist");
813  if (!tmp.isEmpty())
815 
816  tmp = gCoreContext->GetSetting("MusicFormatCompilationFileTrack");
817  if (!tmp.isEmpty())
819 
820  tmp = gCoreContext->GetSetting("MusicFormatCompilationCDArtist");
821  if (!tmp.isEmpty())
823 
824  tmp = gCoreContext->GetSetting("MusicFormatCompilationCDTrack");
825  if (!tmp.isEmpty())
827 }
828 
829 
831 {
832  m_compilation = (!m_compilationArtist.isEmpty()
835  return m_compilation;
836 }
837 
838 
839 inline QString MusicMetadata::formatReplaceSymbols(const QString &format)
840 {
841  QString rv = format;
842  rv.replace("COMPARTIST", m_compilationArtist);
843  rv.replace("ARTIST", m_artist);
844  rv.replace("TITLE", m_title);
845  rv.replace("TRACK", QString("%1").arg(m_trackNum, 2));
846  return rv;
847 }
848 
850 {
851  if (m_artist.isEmpty())
852  m_artist = tr("Unknown Artist", "Default artist if no artist");
853  // This should be the same as Artist if it's a compilation track or blank
854  if (!m_compilation || m_compilationArtist.isEmpty())
856  if (m_album.isEmpty())
857  m_album = tr("Unknown Album", "Default album if no album");
858  if (m_title.isEmpty())
860  if (m_genre.isEmpty())
861  m_genre = tr("Unknown Genre", "Default genre if no genre");
863 }
864 
866 {
867  std::shared_ptr<MythSortHelper>sh = getMythSortHelper();
868 
869  if (m_artistSort.isEmpty() and not m_artist.isEmpty())
870  m_artistSort = sh->doTitle(m_artist);
871  if (m_compilationArtistSort.isEmpty() and not m_compilationArtist.isEmpty())
873  if (m_albumSort.isEmpty() and not m_album.isEmpty())
874  m_albumSort = sh->doTitle(m_album);
875  if (m_titleSort.isEmpty() and not m_title.isEmpty())
876  m_titleSort = sh->doTitle(m_title);
877 }
878 
880 {
881  QString format_artist;
882  QString format_title;
883 
884  if (!m_compilation
885  || "" == m_compilationArtist
887  {
888  if (!cd)
889  {
890  format_artist = s_formatNormalFileArtist;
891  format_title = s_formatNormalFileTrack;
892  }
893  else
894  {
895  format_artist = s_formatNormalCdArtist;
896  format_title = s_formatNormalCdTrack;
897  }
898  }
899  else
900  {
901  if (!cd)
902  {
903  format_artist = s_formatCompilationFileArtist;
904  format_title = s_formatCompilationFileTrack;
905  }
906  else
907  {
908  format_artist = s_formatCompilationCdArtist;
909  format_title = s_formatCompilationCdTrack;
910  }
911  }
912 
913  // NB Could do some comparisons here to save memory with shallow copies...
914  m_formattedArtist = formatReplaceSymbols(format_artist);
915  m_formattedTitle = formatReplaceSymbols(format_title);
916 }
917 
918 
920 {
921  if (m_formattedArtist.isEmpty())
923 
924  return m_formattedArtist;
925 }
926 
927 
929 {
930  if (m_formattedTitle.isEmpty())
932 
933  return m_formattedTitle;
934 }
935 
936 void MusicMetadata::setFilename(const QString& lfilename)
937 {
938  m_filename = lfilename;
939  m_actualFilename.clear();
940 }
941 
943 {
944  // FIXME: for now just use the first url for radio streams
945  if (isRadio())
946  return m_urls[0];
947 
948  // if not asked to find the file just return the raw filename from the DB
949  if (!find)
950  return m_filename;
951 
952  if (!m_actualFilename.isEmpty())
953  return m_actualFilename;
954 
955  // check for a cd track
956  if (m_filename.endsWith(".cda"))
957  {
959  return m_filename;
960  }
961 
962  // check for http urls etc
963  if (m_filename.contains("://"))
964  {
966  return m_filename;
967  }
968 
969  // first check to see if the filename is complete
970  if (QFile::exists(m_filename))
971  {
973  return m_filename;
974  }
975 
976  // maybe it's in our 'Music' storage group
977  QString mythUrl = RemoteFile::FindFile(m_filename, m_hostname, "Music");
978  if (!mythUrl.isEmpty())
979  {
980  m_actualFilename = mythUrl;
981 
982  QUrl url(mythUrl);
983  if (url.host() != m_hostname &&
984  QHostAddress(url.host()).isNull()) // Check that it's not an IP address
985  {
986  m_hostname = url.host();
987  saveHostname();
988  }
989 
990  return mythUrl;
991  }
992 
993  // not found
994  LOG(VB_GENERAL, LOG_ERR, QString("MusicMetadata: Asked to get the filename for a track but no file found: %1")
995  .arg(m_filename));
996 
998 
999  return m_actualFilename;
1000 }
1001 
1004 {
1005  // try the file name as is first
1006  if (QFile::exists(m_filename))
1007  return m_filename;
1008 
1009  // not found so now try to find the file in the local 'Music' storage group
1010  StorageGroup storageGroup("Music", gCoreContext->GetHostName(), false);
1011  return storageGroup.FindFile(m_filename);
1012 }
1013 
1014 void MusicMetadata::setField(const QString &field, const QString &data)
1015 {
1016  if (field == "artist")
1017  m_artist = data;
1018  else if (field == "compilation_artist")
1019  m_compilationArtist = data;
1020  else if (field == "album")
1021  m_album = data;
1022  else if (field == "title")
1023  m_title = data;
1024  else if (field == "genre")
1025  m_genre = data;
1026  else if (field == "filename")
1027  m_filename = data;
1028  else if (field == "year")
1029  m_year = data.toInt();
1030  else if (field == "tracknum")
1031  m_trackNum = data.toInt();
1032  else if (field == "trackcount")
1033  m_trackCount = data.toInt();
1034  else if (field == "discnum")
1035  m_discNum = data.toInt();
1036  else if (field == "disccount")
1037  m_discCount = data.toInt();
1038  else if (field == "length")
1039  m_length = data.toInt();
1040  else if (field == "compilation")
1041  m_compilation = (data.toInt() > 0);
1042  else
1043  {
1044  LOG(VB_GENERAL, LOG_ERR, QString("Something asked me to set data "
1045  "for a field called %1").arg(field));
1046  }
1047  ensureSortFields();
1048 }
1049 
1050 void MusicMetadata::getField(const QString &field, QString *data)
1051 {
1052  if (field == "artist")
1053  *data = FormatArtist();
1054  else if (field == "album")
1055  *data = m_album;
1056  else if (field == "title")
1057  *data = FormatTitle();
1058  else if (field == "genre")
1059  *data = m_genre;
1060  else
1061  {
1062  LOG(VB_GENERAL, LOG_ERR, QString("Something asked me to return data "
1063  "about a field called %1").arg(field));
1064  *data = "I Dunno";
1065  }
1066 }
1067 
1068 void MusicMetadata::toMap(InfoMap &metadataMap, const QString &prefix)
1069 {
1070  using namespace MythDate;
1071  metadataMap[prefix + "songid"] = QString::number(m_id);
1072  metadataMap[prefix + "artist"] = m_artist;
1073  metadataMap[prefix + "formatartist"] = FormatArtist();
1074  metadataMap[prefix + "compilationartist"] = m_compilationArtist;
1075 
1076  if (m_album.isEmpty() && ID_TO_REPO(m_id) == RT_Radio)
1077  {
1078  if (m_broadcaster.isEmpty())
1079  metadataMap[prefix + "album"] = m_channel;
1080  else
1081  metadataMap[prefix + "album"] = QString("%1 - %2").arg(m_broadcaster).arg(m_channel);
1082  }
1083  else
1084  metadataMap[prefix + "album"] = m_album;
1085 
1086  metadataMap[prefix + "title"] = m_title;
1087  metadataMap[prefix + "formattitle"] = FormatTitle();
1088  metadataMap[prefix + "tracknum"] = (m_trackNum > 0 ? QString("%1").arg(m_trackNum) : "");
1089  metadataMap[prefix + "trackcount"] = (m_trackCount > 0 ? QString("%1").arg(m_trackCount) : "");
1090  metadataMap[prefix + "discnum"] = (m_discNum > 0 ? QString("%1").arg(m_discNum) : "");
1091  metadataMap[prefix + "disccount"] = (m_discCount > 0 ? QString("%1").arg(m_discCount) : "");
1092  metadataMap[prefix + "genre"] = m_genre;
1093  metadataMap[prefix + "year"] = (m_year > 0 ? QString("%1").arg(m_year) : "");
1094 
1095  int len = m_length / 1000;
1096  int eh = len / 3600;
1097  int em = (len / 60) % 60;
1098  int es = len % 60;
1099  if (eh > 0)
1100  metadataMap[prefix + "length"] = QString("%1:%2:%3")
1101  .arg(eh,1,10)
1102  .arg(em,2,10,QChar('0'))
1103  .arg(es,2,10,QChar('0'));
1104  else
1105  metadataMap[prefix + "length"] = QString("%1:%2")
1106  .arg(em,2,10,QChar('0'))
1107  .arg(es,2,10,QChar('0'));
1108 
1109  if (m_lastPlay.isValid())
1110  {
1111  metadataMap[prefix + "lastplayed"] =
1113  }
1114  else
1115  {
1116  metadataMap[prefix + "lastplayed"] = tr("Never Played");
1117  }
1118 
1119  metadataMap[prefix + "dateadded"] = MythDate::toString(
1121 
1122  metadataMap[prefix + "playcount"] = QString::number(m_playCount);
1123 
1124  QLocale locale = gCoreContext->GetQLocale();
1125  QString tmpSize = locale.toString(m_fileSize *
1126  (1.0 / (1024.0 * 1024.0)), 'f', 2);
1127  metadataMap[prefix + "filesize"] = tmpSize;
1128 
1129  metadataMap[prefix + "filename"] = m_filename;
1130 
1131  // radio stream
1132  if (!m_broadcaster.isEmpty())
1133  metadataMap[prefix + "broadcasterchannel"] = m_broadcaster + " - " + m_channel;
1134  else
1135  metadataMap[prefix + "broadcasterchannel"] = m_channel;
1136  metadataMap[prefix + "broadcaster"] = m_broadcaster;
1137  metadataMap[prefix + "channel"] = m_channel;
1138  metadataMap[prefix + "genre"] = m_genre;
1139  metadataMap[prefix + "country"] = m_country;
1140  metadataMap[prefix + "language"] = m_language;
1141  metadataMap[prefix + "description"] = m_description;
1142 
1143  if (isRadio())
1144  {
1145  QUrl url(m_urls[0]);
1146  metadataMap[prefix + "url"] = url.toString(QUrl::RemoveUserInfo);
1147  }
1148  else
1149  metadataMap[prefix + "url"] = m_filename;
1150 
1151  metadataMap[prefix + "logourl"] = m_logoUrl;
1152  metadataMap[prefix + "metadataformat"] = m_metaFormat;
1153 }
1154 
1156 {
1157  if (m_rating > 0)
1158  {
1159  m_rating--;
1160  }
1161  m_changed = true;
1162 }
1163 
1165 {
1166  if (m_rating < 10)
1167  {
1168  m_rating++;
1169  }
1170  m_changed = true;
1171 }
1172 
1173 void MusicMetadata::setLastPlay(const QDateTime& lastPlay)
1174 {
1175  m_tempLastPlay = MythDate::as_utc(lastPlay);
1176  m_changed = true;
1177 }
1178 
1180 {
1182  m_changed = true;
1183 }
1184 
1186 {
1188  m_changed = true;
1189 }
1190 
1192 {
1193  // add the images found in the tag to the ones we got from the DB
1194 
1195  if (!m_albumArt)
1196  m_albumArt = new AlbumArtImages(this, false);
1197 
1198  foreach (auto art, albumart)
1199  {
1200  AlbumArtImage *image = art;
1201  image->m_filename = QString("%1-%2").arg(m_id).arg(image->m_filename);
1202  m_albumArt->addImage(art);
1203  }
1204 
1205  m_changed = true;
1206 }
1207 
1208 QStringList MusicMetadata::fillFieldList(const QString& field)
1209 {
1210  QStringList searchList;
1211  searchList.clear();
1212 
1213  MSqlQuery query(MSqlQuery::InitCon());
1214  if ("artist" == field)
1215  {
1216  query.prepare("SELECT artist_name FROM music_artists ORDER BY artist_name;");
1217  }
1218  else if ("compilation_artist" == field)
1219  {
1220  query.prepare("SELECT DISTINCT artist_name FROM music_artists, music_albums where "
1221  "music_albums.artist_id=music_artists.artist_id ORDER BY artist_name");
1222  }
1223  else if ("album" == field)
1224  {
1225  query.prepare("SELECT album_name FROM music_albums ORDER BY album_name;");
1226  }
1227  else if ("title" == field)
1228  {
1229  query.prepare("SELECT name FROM music_songs ORDER BY name;");
1230  }
1231  else if ("genre" == field)
1232  {
1233  query.prepare("SELECT genre FROM music_genres ORDER BY genre;");
1234  }
1235  else
1236  {
1237  return searchList;
1238  }
1239 
1240  if (query.exec() && query.isActive())
1241  {
1242  while (query.next())
1243  {
1244  searchList << query.value(0).toString();
1245  }
1246  }
1247  return searchList;
1248 }
1249 
1251 {
1252  if (!m_albumArt)
1253  m_albumArt = new AlbumArtImages(this);
1254 
1255  AlbumArtImage *albumart_image = nullptr;
1256  QString res;
1257 
1258  if ((albumart_image = m_albumArt->getImage(IT_FRONTCOVER)))
1259  res = albumart_image->m_filename; // NOLINT(bugprone-branch-clone)
1260  else if ((albumart_image = m_albumArt->getImage(IT_UNKNOWN)))
1261  res = albumart_image->m_filename;
1262  else if ((albumart_image = m_albumArt->getImage(IT_BACKCOVER)))
1263  res = albumart_image->m_filename;
1264  else if ((albumart_image = m_albumArt->getImage(IT_INLAY)))
1265  res = albumart_image->m_filename;
1266  else if ((albumart_image = m_albumArt->getImage(IT_CD)))
1267  res = albumart_image->m_filename;
1268 
1269  // check file exists
1270  if (!res.isEmpty() && albumart_image)
1271  {
1272  int repo = ID_TO_REPO(m_id);
1273  if (repo == RT_Radio)
1274  {
1275  // image is a radio station icon, check if we have already downloaded and cached it
1276  QString path = GetConfDir() + "/MythMusic/AlbumArt/";
1277  QFileInfo fi(res);
1278  QString filename = QString("%1-%2.%3").arg(m_id).arg("front").arg(fi.suffix());
1279 
1280  albumart_image->m_filename = path + filename;
1281 
1282  if (!QFile::exists(albumart_image->m_filename))
1283  {
1284  // file does not exist so try to download and cache it
1285  if (!GetMythDownloadManager()->download(res, albumart_image->m_filename))
1286  {
1287  m_albumArt->getImageList()->removeAll(albumart_image);
1288  return QString("");
1289  }
1290  }
1291 
1292  res = albumart_image->m_filename;
1293  }
1294  else
1295  {
1296  // check for the image in the storage group
1297  QUrl url(res);
1298 
1299  if (url.path().isEmpty() || url.host().isEmpty() || url.userName().isEmpty())
1300  {
1301  return QString("");
1302  }
1303 
1304  if (!RemoteFile::Exists(res))
1305  {
1306  if (albumart_image->m_embedded)
1307  {
1308  if (gCoreContext->IsMasterBackend() &&
1309  url.host() == gCoreContext->GetMasterHostName())
1310  {
1311  QStringList paramList;
1312  paramList.append(QString("--songid='%1'").arg(ID()));
1313  paramList.append(QString("--imagetype='%1'").arg(albumart_image->m_imageType));
1314 
1315  QString command = "mythutil --extractimage " + paramList.join(" ");
1316 
1317  QScopedPointer<MythSystem> cmd(MythSystem::Create(command,
1321  }
1322  else
1323  {
1324  QStringList slist;
1325  slist << "MUSIC_TAG_GETIMAGE"
1326  << Hostname()
1327  << QString::number(ID())
1328  << QString::number(albumart_image->m_imageType);
1330  }
1331  }
1332  }
1333  }
1334 
1335  return res;
1336  }
1337 
1338  return QString("");
1339 }
1340 
1342 {
1343  if (!m_albumArt)
1344  m_albumArt = new AlbumArtImages(this);
1345 
1346  AlbumArtImage *albumart_image = m_albumArt->getImage(type);
1347  if (albumart_image)
1348  return albumart_image->m_filename;
1349 
1350  return QString("");
1351 }
1352 
1354 {
1355  if (!m_albumArt)
1356  m_albumArt = new AlbumArtImages(this);
1357 
1358  return m_albumArt;
1359 }
1360 
1362 {
1363  delete m_albumArt;
1364  m_albumArt = nullptr; //new AlbumArtImages(this);
1365 }
1366 
1368 {
1369  if (!m_lyricsData)
1370  m_lyricsData = new LyricsData(this);
1371 
1372  return m_lyricsData;
1373 }
1374 
1375 // create a MetaIO for the file to read/write any tags etc
1376 // NOTE the caller is responsible for deleting it
1378 {
1379  // the taggers require direct file access so try to find
1380  // the file on the local filesystem
1381 
1382  QString filename = getLocalFilename();
1383 
1384  if (!filename.isEmpty())
1385  {
1386  LOG(VB_FILE, LOG_INFO, QString("MusicMetadata::getTagger - creating tagger for %1").arg(filename));
1388  }
1389 
1390  LOG(VB_GENERAL, LOG_ERR, QString("MusicMetadata::getTagger - failed to find %1 on the local filesystem").arg(Filename(false)));
1391  return nullptr;
1392 }
1393 
1394 //--------------------------------------------------------------------------
1395 
1397 {
1398  RunProlog();
1399  //if you want to simulate a big music collection load
1400  //sleep(3);
1401  m_parent->resync();
1402  RunEpilog();
1403 }
1404 
1406 {
1407  // Start a thread to do data loading and sorting
1408  startLoading();
1409 }
1410 
1412 {
1413  while (!m_allMusic.empty())
1414  {
1415  delete m_allMusic.back();
1416  m_allMusic.pop_back();
1417  }
1418 
1419  while (!m_cdData.empty())
1420  {
1421  delete m_cdData.back();
1422  m_cdData.pop_back();
1423  }
1424 
1425  m_metadataLoader->wait();
1426  delete m_metadataLoader;
1427 }
1428 
1430 {
1431  // If this is still running, the user
1432  // probably selected mythmusic and then
1433  // escaped out right away
1434 
1435  if (m_metadataLoader->isFinished())
1436  {
1437  return true;
1438  }
1439 
1440  m_metadataLoader->wait();
1441  return false;
1442 }
1443 
1456 {
1457  // Set this to false early rather than letting it be
1458  // delayed till the thread calls resync.
1459  m_doneLoading = false;
1460 
1461  if (m_metadataLoader)
1462  {
1463  cleanOutThreads();
1464  delete m_metadataLoader;
1465  }
1466 
1467  m_metadataLoader = new MetadataLoadingThread(this);
1468  m_metadataLoader->start();
1469 
1470  return true;
1471 }
1472 
1475 {
1476  uint added = 0;
1477  uint removed = 0;
1478  uint changed = 0;
1479 
1480  m_doneLoading = false;
1481 
1482  QString aquery = "SELECT music_songs.song_id, music_artists.artist_id, music_artists.artist_name, "
1483  "music_comp_artists.artist_name AS compilation_artist, "
1484  "music_albums.album_id, music_albums.album_name, music_songs.name, music_genres.genre, music_songs.year, "
1485  "music_songs.track, music_songs.length, music_songs.directory_id, "
1486  "CONCAT_WS('/', music_directories.path, music_songs.filename) AS filename, "
1487  "music_songs.rating, music_songs.numplays, music_songs.lastplay, music_songs.date_entered, "
1488  "music_albums.compilation, music_songs.format, music_songs.track_count, "
1489  "music_songs.size, music_songs.hostname, music_songs.disc_number, music_songs.disc_count "
1490  "FROM music_songs "
1491  "LEFT JOIN music_directories ON music_songs.directory_id=music_directories.directory_id "
1492  "LEFT JOIN music_artists ON music_songs.artist_id=music_artists.artist_id "
1493  "LEFT JOIN music_albums ON music_songs.album_id=music_albums.album_id "
1494  "LEFT JOIN music_artists AS music_comp_artists ON music_albums.artist_id=music_comp_artists.artist_id "
1495  "LEFT JOIN music_genres ON music_songs.genre_id=music_genres.genre_id "
1496  "ORDER BY music_songs.song_id;";
1497 
1498  QString filename;
1499  QString artist;
1500  QString album;
1501  QString title;
1502  QString compartist;
1503 
1504  MSqlQuery query(MSqlQuery::InitCon());
1505  if (!query.exec(aquery))
1506  MythDB::DBError("AllMusic::resync", query);
1507 
1508  m_numPcs = query.size() * 2;
1509  m_numLoaded = 0;
1510  QList<MusicMetadata::IdType> idList;
1511 
1512  if (query.isActive() && query.size() > 0)
1513  {
1514  while (query.next())
1515  {
1516  MusicMetadata::IdType id = query.value(0).toInt();
1517 
1518  idList.append(id);
1519 
1520  auto *dbMeta = new MusicMetadata(
1521  query.value(12).toString(), // filename
1522  query.value(2).toString(), // artist
1523  query.value(3).toString(), // compilation artist
1524  query.value(5).toString(), // album
1525  query.value(6).toString(), // title
1526  query.value(7).toString(), // genre
1527  query.value(8).toInt(), // year
1528  query.value(9).toInt(), // track no.
1529  query.value(10).toInt(), // length
1530  query.value(0).toInt(), // id
1531  query.value(13).toInt(), // rating
1532  query.value(14).toInt(), // playcount
1533  query.value(15).toDateTime(), // lastplay
1534  query.value(16).toDateTime(), // date_entered
1535  (query.value(17).toInt() > 0), // compilation
1536  query.value(18).toString()); // format
1537 
1538  dbMeta->setDirectoryId(query.value(11).toInt());
1539  dbMeta->setArtistId(query.value(1).toInt());
1540  dbMeta->setAlbumId(query.value(4).toInt());
1541  dbMeta->setTrackCount(query.value(19).toInt());
1542  dbMeta->setFileSize(query.value(20).toULongLong());
1543  dbMeta->setHostname(query.value(21).toString());
1544  dbMeta->setDiscNumber(query.value(22).toInt());
1545  dbMeta->setDiscCount(query.value(23).toInt());
1546 
1547  if (!m_musicMap.contains(id))
1548  {
1549  // new track
1550 
1551  // Don't delete dbMeta, as the MetadataPtrList now owns it
1552  m_allMusic.append(dbMeta);
1553 
1554  m_musicMap[id] = dbMeta;
1555 
1556  added++;
1557  }
1558  else
1559  {
1560  // existing track, check for any changes
1561  MusicMetadata *cacheMeta = m_musicMap[id];
1562 
1563  if (cacheMeta && !cacheMeta->compare(dbMeta))
1564  {
1565  cacheMeta->reloadMetadata();
1566  changed++;
1567  }
1568 
1569  // we already have this track in the cache so don't need dbMeta anymore
1570  delete dbMeta;
1571  }
1572 
1573  // compute max/min playcount,lastplay for all music
1574  if (query.at() == 0)
1575  {
1576  // first song
1577  m_playCountMin = m_playCountMax = query.value(13).toInt();
1578 #if QT_VERSION < QT_VERSION_CHECK(5,8,0)
1579  m_lastPlayMin = m_lastPlayMax = query.value(14).toDateTime().toTime_t();
1580 #else
1581  m_lastPlayMin = m_lastPlayMax = query.value(14).toDateTime().toSecsSinceEpoch();
1582 #endif
1583  }
1584  else
1585  {
1586  int playCount = query.value(13).toInt();
1587 #if QT_VERSION < QT_VERSION_CHECK(5,8,0)
1588  double lastPlay = query.value(14).toDateTime().toTime_t();
1589 #else
1590  qint64 lastPlay = query.value(14).toDateTime().toSecsSinceEpoch();
1591 #endif
1592 
1593  m_playCountMin = min(playCount, m_playCountMin);
1594  m_playCountMax = max(playCount, m_playCountMax);
1595  m_lastPlayMin = min(lastPlay, m_lastPlayMin);
1596  m_lastPlayMax = max(lastPlay, m_lastPlayMax);
1597  }
1598  m_numLoaded++;
1599  }
1600  }
1601  else
1602  {
1603  LOG(VB_GENERAL, LOG_ERR, "MythMusic hasn't found any tracks!");
1604  }
1605 
1606  // get a list of tracks in our cache that's now not in the database
1607  QList<MusicMetadata::IdType> deleteList;
1608  foreach (auto track, m_allMusic)
1609  {
1610  if (!idList.contains(track->ID()))
1611  {
1612  deleteList.append(track->ID());
1613  }
1614  }
1615 
1616  // remove the no longer available tracks
1617  for (uint id : deleteList)
1618  {
1619  MusicMetadata *mdata = m_musicMap[id];
1620  m_allMusic.removeAll(mdata);
1621  m_musicMap.remove(id);
1622  removed++;
1623  delete mdata;
1624  }
1625 
1626  // tell any listeners a resync has just finished and they may need to reload/resync
1627  LOG(VB_GENERAL, LOG_DEBUG, QString("AllMusic::resync sending MUSIC_RESYNC_FINISHED added: %1, removed: %2, changed: %3")
1628  .arg(added).arg(removed).arg(changed));
1629  gCoreContext->SendMessage(QString("MUSIC_RESYNC_FINISHED %1 %2 %3").arg(added).arg(removed).arg(changed));
1630 
1631  m_doneLoading = true;
1632 }
1633 
1635 {
1636  if (m_musicMap.contains(an_id))
1637  return m_musicMap[an_id];
1638 
1639  return nullptr;
1640 }
1641 
1642 bool AllMusic::isValidID(int an_id)
1643 {
1644  return m_musicMap.contains(an_id);
1645 }
1646 
1647 bool AllMusic::updateMetadata(int an_id, MusicMetadata *the_track)
1648 {
1649  if (an_id > 0)
1650  {
1651  MusicMetadata *mdata = getMetadata(an_id);
1652  if (mdata)
1653  {
1654  *mdata = *the_track;
1655  return true;
1656  }
1657  }
1658  return false;
1659 }
1660 
1662 void AllMusic::save(void)
1663 {
1664  foreach (auto & item, m_allMusic)
1665  {
1666  if (item->hasChanged())
1667  item->persist();
1668  }
1669 }
1670 
1671 // cd stuff
1673 {
1674  while (!m_cdData.empty())
1675  {
1676  MusicMetadata *mdata = m_cdData.back();
1677  if (m_musicMap.contains(mdata->ID()))
1678  m_musicMap.remove(mdata->ID());
1679 
1680  delete m_cdData.back();
1681  m_cdData.pop_back();
1682  }
1683 
1684  m_cdTitle = tr("CD -- none");
1685 }
1686 
1687 void AllMusic::addCDTrack(const MusicMetadata &the_track)
1688 {
1689  auto *mdata = new MusicMetadata(the_track);
1690  mdata->setID(m_cdData.count() + 1);
1691  mdata->setRepo(RT_CD);
1692  m_cdData.append(mdata);
1693  m_musicMap[mdata->ID()] = mdata;
1694 }
1695 
1697 {
1698  if (m_cdData.count() < 1)
1699  return false;
1700 
1701  return m_cdData.last()->FormatTitle() == the_track->FormatTitle();
1702 }
1703 
1705 {
1706  foreach (auto & anit, m_cdData)
1707  {
1708  if (anit->Track() == the_track)
1709  {
1710  return anit;
1711  }
1712  }
1713 
1714  return nullptr;
1715 }
1716 
1717 /**************************************************************************/
1718 
1720 {
1721  loadStreams();
1722 }
1723 
1725 {
1726  while (!m_streamList.empty())
1727  {
1728  delete m_streamList.back();
1729  m_streamList.pop_back();
1730  }
1731 }
1732 
1734 {
1735  for (int x = 0; x < m_streamList.count(); x++)
1736  {
1737  if (m_streamList.at(x)->ID() == an_id)
1738  return true;
1739  }
1740 
1741  return false;
1742 }
1743 
1745 {
1746  for (int x = 0; x < m_streamList.count(); x++)
1747  {
1748  if (m_streamList.at(x)->ID() == an_id)
1749  return m_streamList.at(x);
1750  }
1751 
1752  return nullptr;
1753 }
1754 
1756 {
1757  while (!m_streamList.empty())
1758  {
1759  delete m_streamList.back();
1760  m_streamList.pop_back();
1761  }
1762 
1763  QString aquery = "SELECT intid, broadcaster, channel, description, url1, url2, url3, url4, url5,"
1764  "logourl, genre, metaformat, country, language, format "
1765  "FROM music_radios "
1766  "ORDER BY broadcaster,channel;";
1767 
1768  MSqlQuery query(MSqlQuery::InitCon());
1769  if (!query.exec(aquery))
1770  MythDB::DBError("AllStream::loadStreams", query);
1771 
1772  if (query.isActive() && query.size() > 0)
1773  {
1774  while (query.next())
1775  {
1776  UrlList urls;
1777  for (int x = 0; x < STREAMURLCOUNT; x++)
1778  urls[x] = query.value(4 + x).toString();
1779 
1780  auto *mdata = new MusicMetadata(
1781  query.value(0).toInt(), // intid
1782  query.value(1).toString(), // broadcaster
1783  query.value(2).toString(), // channel
1784  query.value(3).toString(), // description
1785  urls, // array of 5 urls
1786  query.value(9).toString(), // logourl
1787  query.value(10).toString(), // genre
1788  query.value(11).toString(), // metadataformat
1789  query.value(12).toString(), // country
1790  query.value(13).toString(), // language
1791  query.value(14).toString()); // format
1792 
1793  mdata->setRepo(RT_Radio);
1794 
1795  m_streamList.append(mdata);
1796  }
1797  }
1798  else
1799  {
1800  LOG(VB_GENERAL, LOG_WARNING, "MythMusic hasn't found any radio streams!");
1801  }
1802 }
1803 
1805 {
1806  // add the stream to the db
1807  MSqlQuery query(MSqlQuery::InitCon());
1808  query.prepare("INSERT INTO music_radios (broadcaster, channel, description, "
1809  "url1, url2, url3, url4, url5, "
1810  "logourl, genre, country, language, format, metaformat) "
1811  "VALUES (:BROADCASTER, :CHANNEL, :DESCRIPTION, :URL1, :URL2, :URL3, :URL4, :URL5, "
1812  ":LOGOURL, :GENRE, :COUNTRY, :LANGUAGE, :FORMAT, :METAFORMAT);");
1813  query.bindValueNoNull(":BROADCASTER", mdata->Broadcaster());
1814  query.bindValueNoNull(":CHANNEL", mdata->Channel());
1815  query.bindValueNoNull(":DESCRIPTION", mdata->Description());
1816  query.bindValueNoNull(":URL1", mdata->Url(0));
1817  query.bindValueNoNull(":URL2", mdata->Url(1));
1818  query.bindValueNoNull(":URL3", mdata->Url(2));
1819  query.bindValueNoNull(":URL4", mdata->Url(3));
1820  query.bindValueNoNull(":URL5", mdata->Url(4));
1821  query.bindValueNoNull(":LOGOURL", mdata->LogoUrl());
1822  query.bindValueNoNull(":GENRE", mdata->Genre());
1823  query.bindValueNoNull(":COUNTRY", mdata->Country());
1824  query.bindValueNoNull(":LANGUAGE", mdata->Language());
1825  query.bindValueNoNull(":FORMAT", mdata->Format());
1826  query.bindValueNoNull(":METAFORMAT", mdata->MetadataFormat());
1827 
1828  if (!query.exec() || !query.isActive() || query.numRowsAffected() <= 0)
1829  {
1830  MythDB::DBError("music insert radio", query);
1831  return;
1832  }
1833 
1834  mdata->setID(query.lastInsertId().toInt());
1835  mdata->setRepo(RT_Radio);
1836 
1837  loadStreams();
1838 }
1839 
1841 {
1842  // remove the stream from the db
1843  int id = ID_TO_ID(mdata->ID());
1844  MSqlQuery query(MSqlQuery::InitCon());
1845  query.prepare("DELETE FROM music_radios WHERE intid = :ID");
1846  query.bindValue(":ID", id);
1847 
1848  if (!query.exec() || query.numRowsAffected() <= 0)
1849  {
1850  MythDB::DBError("AllStream::removeStream", query);
1851  return;
1852  }
1853 
1854  loadStreams();
1855 }
1856 
1858 {
1859  // update the stream in the db
1860  int id = ID_TO_ID(mdata->ID());
1861  MSqlQuery query(MSqlQuery::InitCon());
1862  query.prepare("UPDATE music_radios set broadcaster = :BROADCASTER, channel = :CHANNEL, description = :DESCRIPTION, "
1863  "url1 = :URL1, url2 = :URL2, url3 = :URL3, url4 = :URL4, url5 = :URL5, "
1864  "logourl = :LOGOURL, genre = :GENRE, country = :COUNTRY, language = :LANGUAGE, "
1865  "format = :FORMAT, metaformat = :METAFORMAT "
1866  "WHERE intid = :ID");
1867  query.bindValueNoNull(":BROADCASTER", mdata->Broadcaster());
1868  query.bindValueNoNull(":CHANNEL", mdata->Channel());
1869  query.bindValueNoNull(":DESCRIPTION", mdata->Description());
1870  query.bindValueNoNull(":URL1", mdata->Url(0));
1871  query.bindValueNoNull(":URL2", mdata->Url(1));
1872  query.bindValueNoNull(":URL3", mdata->Url(2));
1873  query.bindValueNoNull(":URL4", mdata->Url(3));
1874  query.bindValueNoNull(":URL5", mdata->Url(4));
1875  query.bindValueNoNull(":LOGOURL", mdata->LogoUrl());
1876  query.bindValueNoNull(":GENRE", mdata->Genre());
1877  query.bindValueNoNull(":COUNTRY", mdata->Country());
1878  query.bindValueNoNull(":LANGUAGE", mdata->Language());
1879  query.bindValueNoNull(":FORMAT", mdata->Format());
1880  query.bindValueNoNull(":METAFORMAT", mdata->MetadataFormat());
1881  query.bindValue(":ID", id);
1882 
1883  if (!query.exec() || !query.isActive() || query.numRowsAffected() <= 0)
1884  {
1885  MythDB::DBError("AllStream::updateStream", query);
1886  return;
1887  }
1888 
1889  loadStreams();
1890 }
1891 
1892 /**************************************************************************/
1893 
1895  : m_parent(metadata)
1896 {
1897  if (loadFromDB)
1898  findImages();
1899 }
1900 
1902 {
1903  while (!m_imageList.empty())
1904  {
1905  delete m_imageList.back();
1906  m_imageList.pop_back();
1907  }
1908 }
1909 
1911 {
1912  while (!m_imageList.empty())
1913  {
1914  delete m_imageList.back();
1915  m_imageList.pop_back();
1916  }
1917 
1918  if (m_parent == nullptr)
1919  return;
1920 
1921  int trackid = ID_TO_ID(m_parent->ID());
1922  int repo = ID_TO_REPO(m_parent->ID());
1923 
1924  if (repo == RT_Radio)
1925  {
1926  MSqlQuery query(MSqlQuery::InitCon());
1927  //FIXME: this should work with the alternate urls as well eg url2, url3 etc
1928  query.prepare("SELECT logourl FROM music_radios WHERE url1 = :URL;");
1929  query.bindValue(":URL", m_parent->Filename());
1930  if (query.exec())
1931  {
1932  while (query.next())
1933  {
1934  QString logoUrl = query.value(0).toString();
1935 
1936  auto *image = new AlbumArtImage();
1937  image->m_id = -1;
1938  image->m_filename = logoUrl;
1939  image->m_imageType = IT_FRONTCOVER;
1940  image->m_embedded = false;
1941  image->m_hostname = "";
1942  m_imageList.push_back(image);
1943  }
1944  }
1945  }
1946  else
1947  {
1948  if (trackid == 0)
1949  return;
1950 
1951  QFileInfo fi(m_parent->Filename(false));
1952  QString dir = fi.path();
1953 
1954  MSqlQuery query(MSqlQuery::InitCon());
1955  query.prepare("SELECT albumart_id, CONCAT_WS('/', music_directories.path, "
1956  "music_albumart.filename), music_albumart.filename, music_albumart.imagetype, "
1957  "music_albumart.embedded, music_albumart.hostname "
1958  "FROM music_albumart "
1959  "LEFT JOIN music_directories ON "
1960  "music_directories.directory_id = music_albumart.directory_id "
1961  "WHERE music_directories.path = :DIR "
1962  "OR song_id = :SONGID "
1963  "ORDER BY music_albumart.imagetype;");
1964  query.bindValue(":DIR", dir);
1965  query.bindValue(":SONGID", trackid);
1966  if (query.exec())
1967  {
1968  while (query.next())
1969  {
1970  auto *image = new AlbumArtImage();
1971  bool embedded = (query.value(4).toInt() == 1);
1972  image->m_id = query.value(0).toInt();
1973 
1974  QUrl url(m_parent->Filename(true));
1975 
1976  if (embedded)
1977  {
1978  if (url.scheme() == "myth")
1979  {
1980  image->m_filename = MythCoreContext::GenMythURL(url.host(), url.port(),
1981  QString("AlbumArt/") + query.value(1).toString(),
1982  "MusicArt");
1983  }
1984  else
1985  {
1986  image->m_filename = query.value(1).toString();
1987  }
1988  }
1989  else
1990  {
1991  if (url.scheme() == "myth")
1992  {
1993  image->m_filename = MythCoreContext::GenMythURL(url.host(), url.port(),
1994  query.value(1).toString(),
1995  "Music");
1996  }
1997  else
1998  {
1999  image->m_filename = query.value(1).toString();
2000  }
2001  }
2002 
2003  image->m_imageType = (ImageType) query.value(3).toInt();
2004  image->m_embedded = embedded;
2005  image->m_hostname = query.value(5).toString();
2006 
2007  m_imageList.push_back(image);
2008  }
2009  }
2010 
2011  // add any artist images
2012  QString artist = m_parent->Artist().toLower();
2013  if (findIcon("artist", artist) != QString())
2014  {
2015  auto *image = new AlbumArtImage();
2016  image->m_id = -1;
2017  image->m_filename = findIcon("artist", artist);
2018  image->m_imageType = IT_ARTIST;
2019  image->m_embedded = false;
2020 
2021  m_imageList.push_back(image);
2022  }
2023  }
2024 }
2025 
2027 {
2028  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
2029  auto *busy = new MythUIBusyDialog(tr("Scanning for music album art..."),
2030  popupStack, "scanbusydialog");
2031 
2032  if (busy->Create())
2033  {
2034  popupStack->AddScreen(busy, false);
2035  }
2036  else
2037  {
2038  delete busy;
2039  busy = nullptr;
2040  }
2041 
2042  QStringList strList;
2043  strList << "MUSIC_FIND_ALBUMART"
2044  << m_parent->Hostname()
2045  << QString::number(m_parent->ID())
2046  << "1";
2047 
2048  auto *scanThread = new AlbumArtScannerThread(strList);
2049  scanThread->start();
2050 
2051  while (scanThread->isRunning())
2052  {
2053  qApp->processEvents();
2054  usleep(1000);
2055  }
2056 
2057  strList = scanThread->getResult();
2058 
2059  delete scanThread;
2060 
2061  if (busy)
2062  busy->Close();
2063 
2064  while (!m_imageList.empty())
2065  {
2066  delete m_imageList.back();
2067  m_imageList.pop_back();
2068  }
2069 
2070  for (int x = 2; x < strList.count(); x += 6)
2071  {
2072  auto *image = new AlbumArtImage;
2073  image->m_id = strList[x].toInt();
2074  image->m_imageType = (ImageType) strList[x + 1].toInt();
2075  image->m_embedded = (strList[x + 2].toInt() == 1);
2076  image->m_description = strList[x + 3];
2077 
2078  if (image->m_embedded)
2079  {
2080  image->m_filename = MythCoreContext::GenMythURL(m_parent->Hostname(), 0,
2081  QString("AlbumArt/") + strList[x + 4],
2082  "MusicArt");
2083  }
2084  else
2085  {
2086  image->m_filename = MythCoreContext::GenMythURL(m_parent->Hostname(), 0,
2087  strList[x + 4],
2088  "Music");
2089  }
2090 
2091  image->m_hostname = strList[x + 5];
2092 
2093  LOG(VB_FILE, LOG_INFO, "AlbumArtImages::scanForImages found image");
2094  LOG(VB_FILE, LOG_INFO, QString("ID: %1").arg(image->m_id));
2095  LOG(VB_FILE, LOG_INFO, QString("ImageType: %1").arg(image->m_imageType));
2096  LOG(VB_FILE, LOG_INFO, QString("Embedded: %1").arg(image->m_embedded));
2097  LOG(VB_FILE, LOG_INFO, QString("Description: %1").arg(image->m_description));
2098  LOG(VB_FILE, LOG_INFO, QString("Filename: %1").arg(image->m_filename));
2099  LOG(VB_FILE, LOG_INFO, QString("Hostname: %1").arg(image->m_hostname));
2100  LOG(VB_FILE, LOG_INFO, "-------------------------------");
2101 
2102  addImage(image);
2103 
2104  delete image;
2105  }
2106 }
2107 
2109 {
2110  foreach (auto & item, m_imageList)
2111  {
2112  if (item->m_imageType == type)
2113  return item;
2114  }
2115 
2116  return nullptr;
2117 }
2118 
2120 {
2121  foreach (auto & item, m_imageList)
2122  {
2123  if (item->m_id == imageID)
2124  return item;
2125  }
2126 
2127  return nullptr;
2128 }
2129 
2130 QStringList AlbumArtImages::getImageFilenames(void) const
2131 {
2132  QStringList paths;
2133 
2134  foreach (auto item, m_imageList)
2135  paths += item->m_filename;
2136 
2137  return paths;
2138 }
2139 
2141 {
2142  if (index < (uint)m_imageList.size())
2143  return m_imageList[index];
2144 
2145  return nullptr;
2146 }
2147 
2148 // static method to get a translated type name from an ImageType
2150 {
2151  // these const's should match the ImageType enum's
2152  static const char* s_typeStrings[] = {
2153  QT_TR_NOOP("Unknown"), // IT_UNKNOWN
2154  QT_TR_NOOP("Front Cover"), // IT_FRONTCOVER
2155  QT_TR_NOOP("Back Cover"), // IT_BACKCOVER
2156  QT_TR_NOOP("CD"), // IT_CD
2157  QT_TR_NOOP("Inlay"), // IT_INLAY
2158  QT_TR_NOOP("Artist"), // IT_ARTIST
2159  };
2160 
2161  return QCoreApplication::translate("AlbumArtImages",
2162  s_typeStrings[type]);
2163 }
2164 
2165 // static method to get a filename from an ImageType
2167 {
2168  // these const's should match the ImageType enum's
2169  static const char* s_filenameStrings[] = {
2170  QT_TR_NOOP("unknown"), // IT_UNKNOWN
2171  QT_TR_NOOP("front"), // IT_FRONTCOVER
2172  QT_TR_NOOP("back"), // IT_BACKCOVER
2173  QT_TR_NOOP("cd"), // IT_CD
2174  QT_TR_NOOP("inlay"), // IT_INLAY
2175  QT_TR_NOOP("artist") // IT_ARTIST
2176  };
2177 
2178  return QCoreApplication::translate("AlbumArtImages",
2179  s_filenameStrings[type]);
2180 }
2181 
2182 // static method to guess the image type from the filename
2184 {
2186 
2187  if (filename.contains("front", Qt::CaseInsensitive) ||
2188  filename.contains(tr("front"), Qt::CaseInsensitive) ||
2189  filename.contains("cover", Qt::CaseInsensitive) ||
2190  filename.contains(tr("cover"), Qt::CaseInsensitive))
2191  type = IT_FRONTCOVER;
2192  else if (filename.contains("back", Qt::CaseInsensitive) ||
2193  filename.contains(tr("back"), Qt::CaseInsensitive))
2194  type = IT_BACKCOVER;
2195  else if (filename.contains("inlay", Qt::CaseInsensitive) ||
2196  filename.contains(tr("inlay"), Qt::CaseInsensitive))
2197  type = IT_INLAY;
2198  else if (filename.contains("cd", Qt::CaseInsensitive) ||
2199  filename.contains(tr("cd"), Qt::CaseInsensitive))
2200  type = IT_CD;
2201 
2202  return type;
2203 }
2204 
2205 // static method to get image type from the type name
2207 {
2209 
2210  if (name.toLower() == "front")
2211  type = IT_FRONTCOVER;
2212  else if (name.toLower() == "back")
2213  type = IT_BACKCOVER;
2214  else if (name.toLower() == "inlay")
2215  type = IT_INLAY;
2216  else if (name.toLower() == "cd")
2217  type = IT_CD;
2218  else if (name.toLower() == "artist")
2219  type = IT_ARTIST;
2220  else if (name.toLower() == "unknown")
2221  type = IT_UNKNOWN;
2222 
2223  return type;
2224 }
2225 
2227 {
2228  // do we already have an image of this type?
2229  AlbumArtImage *image = nullptr;
2230 
2231  foreach (auto & item, m_imageList)
2232  {
2233  if (item->m_imageType == newImage.m_imageType
2234  && item->m_embedded == newImage.m_embedded)
2235  {
2236  image = item;
2237  break;
2238  }
2239  }
2240 
2241  if (!image)
2242  {
2243  // not found so just add it to the list
2244  image = new AlbumArtImage(newImage);
2245  m_imageList.push_back(image);
2246  }
2247  else
2248  {
2249  // we already have an image of this type so just update it with the new info
2250  image->m_filename = newImage.m_filename;
2251  image->m_imageType = newImage.m_imageType;
2252  image->m_embedded = newImage.m_embedded;
2253  image->m_description = newImage.m_description;
2254  image->m_hostname = newImage.m_hostname;
2255  }
2256 }
2257 
2260 {
2261  MusicMetadata::IdType trackID = ID_TO_ID(m_parent->ID());
2262  int directoryID = m_parent->getDirectoryId();
2263 
2264  // sanity check we have a valid songid and directoryid
2265  if (trackID == 0 || directoryID == -1)
2266  {
2267  LOG(VB_GENERAL, LOG_ERR, "AlbumArtImages: Asked to save to the DB but "
2268  "have invalid songid or directoryid");
2269  return;
2270  }
2271 
2272  MSqlQuery query(MSqlQuery::InitCon());
2273 
2274  // remove all albumart for this track from the db
2275  query.prepare("DELETE FROM music_albumart "
2276  "WHERE song_id = :SONGID "
2277  "OR (embedded = 0 AND directory_id = :DIRECTORYID)");
2278 
2279  query.bindValue(":SONGID", trackID);
2280  query.bindValue(":DIRECTORYID", directoryID);
2281 
2282  if (!query.exec())
2283  {
2284  MythDB::DBError("AlbumArtImages::dumpToDatabase - "
2285  "deleting existing albumart", query);
2286  }
2287 
2288  // now add the albumart to the db
2289  foreach (auto image, m_imageList)
2290  {
2291  //TODO: for the moment just ignore artist images
2292  if (image->m_imageType == IT_ARTIST)
2293  continue;
2294 
2295  if (image->m_id > 0)
2296  {
2297  // re-use the same id this image had before
2298  query.prepare("INSERT INTO music_albumart ( albumart_id, "
2299  "filename, imagetype, song_id, directory_id, embedded, hostname ) "
2300  "VALUES ( :ID, :FILENAME, :TYPE, :SONGID, :DIRECTORYID, :EMBED, :HOSTNAME );");
2301  query.bindValue(":ID", image->m_id);
2302  }
2303  else
2304  {
2305  query.prepare("INSERT INTO music_albumart ( filename, "
2306  "imagetype, song_id, directory_id, embedded, hostname ) VALUES ( "
2307  ":FILENAME, :TYPE, :SONGID, :DIRECTORYID, :EMBED, :HOSTNAME );");
2308  }
2309 
2310  QFileInfo fi(image->m_filename);
2311  query.bindValue(":FILENAME", fi.fileName());
2312 
2313  query.bindValue(":TYPE", image->m_imageType);
2314  query.bindValue(":SONGID", image->m_embedded ? trackID : 0);
2315  query.bindValue(":DIRECTORYID", image->m_embedded ? 0 : directoryID);
2316  query.bindValue(":EMBED", image->m_embedded);
2317  query.bindValue(":HOSTNAME", image->m_hostname);
2318 
2319  if (!query.exec())
2320  {
2321  MythDB::DBError("AlbumArtImages::dumpToDatabase - "
2322  "add/update music_albumart", query);
2323  }
2324  else
2325  {
2326  if (image->m_id <= 0)
2327  image->m_id = query.lastInsertId().toInt();
2328  }
2329  }
2330 }
QList< AlbumArtImage * > AlbumArtList
Definition: musicmetadata.h:56
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:783
QString FormatArtist()
#define ID_TO_REPO(x)
Definition: musicmetadata.h:71
void run() override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
void persist(void)
bool checkCDTrack(MusicMetadata *the_track)
AlbumArtImages(MusicMetadata *metadata, bool loadFromDB=true)
uint64_t m_fileSize
MusicMetadata(QString lfilename="", QString lartist="", QString lcompilation_artist="", QString lalbum="", QString ltitle="", QString lgenre="", int lyear=0, int ltracknum=0, int llength=0, int lid=0, int lrating=0, int lplaycount=0, QDateTime llastplay=QDateTime(), QDateTime ldateadded=QDateTime(), bool lcompilation=false, QString lformat="")
Definition: musicmetadata.h:88
QString FormatTitle()
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:864
QString[STREAMURLCOUNT] UrlList
Definition: musicmetadata.h:78
void addCDTrack(const MusicMetadata &the_track)
QString m_compilationArtistSort
static MusicMetadata * createFromID(int trackid)
static QString s_formatNormalFileArtist
bool isRadio(void) const
QString formatReplaceSymbols(const QString &format)
QString Url(uint index=0)
static QString s_formatCompilationCdTrack
#define STREAMUPDATEURL
Definition: musicmetadata.h:75
bool cleanOutThreads()
QDateTime lastUpdate(GrabberScript *script)
Definition: netutils.cpp:338
QHash< QString, QString > InfoMap
Definition: mythtypes.h:15
QString LogoUrl(void)
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:30
QDateTime m_lastPlay
QString Language(void)
void updateStream(MusicMetadata *mdata)
QString m_titleSort
uint32_t IdType
Definition: musicmetadata.h:86
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:125
QString Genre() const
static pid_list_t::iterator find(const PIDInfoMap &map, pid_list_t &list, pid_list_t::iterator begin, pid_list_t::iterator end, bool find_open)
QString Hostname(void)
QString m_actualFilename
static QString FindFile(const QString &filename, const QString &host, const QString &storageGroup, bool useRegex=false, bool allowFallback=false)
Search all BE's for a file in the give storage group.
void addStream(MusicMetadata *mdata)
void checkEmptyFields(void)
QString getLocalFilename(void)
try to find the track on the local file system
static MetaIO * createTagger(const QString &filename)
Finds an appropriate tagger for the given file.
Definition: metaio.cpp:32
bool operator==(MusicMetadata &a, MusicMetadata &b)
QString m_description
Definition: musicmetadata.h:52
int size(void) const
Definition: mythdbcon.h:203
bool startLoading(void)
Start loading metadata.
static QString getTypeFilename(ImageType type)
AlbumArtImages * getAlbumArtImages(void)
bool download(const QString &url, const QString &dest, bool reload=false)
Downloads a URL to a file in blocking mode.
void findImages(void)
void getField(const QString &field, QString *data)
QString m_filename
QString MetadataFormat(void)
static QStringList fillFieldList(const QString &field)
MythScreenStack * GetStack(const QString &stackname)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
QString m_formattedTitle
void setFilename(const QString &lfilename)
MusicMetadata * m_parent
QString Country(void)
Do Today/Yesterday/Tomorrow transform.
Definition: mythdate.h:23
static QString getTypeName(ImageType type)
void save()
Check each MusicMetadata entry and save those that have changed (ratings, etc.)
QDateTime m_dateAdded
AlbumArtList * getImageList(void)
AlbumArtImage * getImageByID(int imageID)
#define STREAMURLCOUNT
Definition: musicmetadata.h:76
static guint32 * tmp
Definition: goom_core.c:35
QString m_artistSort
QDateTime as_utc(const QDateTime &old_dt)
Returns copy of QDateTime with TimeSpec set to UTC.
Definition: mythdate.cpp:23
QDateTime m_tempLastPlay
void SendMessage(const QString &message)
AlbumArtList m_imageList
void scanForImages(void)
ImageType m_imageType
Definition: musicmetadata.h:51
QString m_hostname
Definition: musicmetadata.h:50
bool isValidID(MusicMetadata::IdType an_id)
Definition: metaio.h:17
QString GetConfDir(void)
Definition: mythdirs.cpp:224
void reloadAlbumArtImages(void)
QVariant value(int i) const
Definition: mythdbcon.h:198
static QString s_formatCompilationCdArtist
QString m_compilationArtist
QString m_format
QString Artist() const
void dumpToDatabase(void)
bool SendReceiveStringList(QStringList &strlist, bool quickTimeout=false, bool block=true)
Send a message to the backend and wait for a response.
bool updateMetadata(int an_id, MusicMetadata *the_track)
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
static bool updateStreamList(void)
MusicMetadata & operator=(const MusicMetadata &rhs)
LyricsData * m_lyricsData
MusicMetadata * getMetadata(int an_id)
QString Channel(void)
QVariant lastInsertId()
Return the id of the last inserted row.
Definition: mythdbcon.cpp:888
QString m_language
QString m_metaFormat
static ImageType getImageTypeFromName(const QString &name)
QString m_filename
Definition: musicmetadata.h:49
QString m_logoUrl
static QString GenMythURL(const QString &host=QString(), int port=0, QString path=QString(), const QString &storageGroup=QString())
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
QString GetSetting(const QString &key, const QString &defaultval="")
MusicMetadata * getMetadata(MusicMetadata::IdType an_id)
AlbumArtImages * m_albumArt
Default local time.
Definition: mythdate.h:16
IdType ID() const
QString findIcon(const QString &type, const QString &name, bool ignoreCache)
find an image for a artist or genre
Definition: musicutils.cpp:34
QByteArray gzipUncompress(const QByteArray &data)
bool isActive(void) const
Definition: mythdbcon.h:204
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
QString getAlbumArtFile(void)
static MythSystem * Create(const QStringList &args, uint flags=kMSNone, const QString &startPath=QString(), Priority cpuPriority=kInheritPriority, Priority diskPriority=kInheritPriority)
Definition: mythsystem.cpp:204
QString GetMasterHostName(void)
void setRepo(RepoType repo)
Add year to string if not included.
Definition: mythdate.h:22
QDateTime GetLastModified(const QString &url)
Gets the Last Modified timestamp for a URI.
unsigned int uint
Definition: compat.h:140
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:535
QString m_channel
QString m_hostname
void setCompilationFormatting(bool cd=false)
void resync()
resync our cache with the database
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:101
void saveHostname(void)
QString m_formattedArtist
QString Format() const
QString m_description
MythMainWindow * GetMythMainWindow(void)
MetaIO * getTagger(void)
static ImageType guessImageType(const QString &filename)
QString m_artist
AlbumArtImage * getImageAt(uint index)
void setUrl(const QString &url, uint index=0)
void dumpToDatabase(void)
saves or updates the image details in the DB
static bool Exists(const QString &url, struct stat *fileinfo)
Definition: remotefile.cpp:460
automatically delete if backgrounded
Definition: mythsystem.h:43
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:808
process events while waiting
Definition: mythsystem.h:37
static QString s_formatNormalCdArtist
QString m_albumSort
QStringList getImageFilenames(void) const
QString Description(void)
AlbumArtImage * getImage(ImageType type)
void clearCDData(void)
QLocale GetQLocale(void)
static QString s_formatNormalCdTrack
static QString s_formatCompilationFileTrack
LyricsData * getLyricsData(void)
avoid disabling UI drawing
Definition: mythsystem.h:35
QString FindFile(const QString &filename)
void bindValueNoNull(const QString &placeholder, const QVariant &val)
Add a single binding, taking care not to set a NULL value.
Definition: mythdbcon.cpp:869
static QString thePrefix
static QString s_formatCompilationFileArtist
bool compare(MusicMetadata *mdata) const
QString Filename(bool find=true)
void reloadMetadata(void)
run child in the background
Definition: mythsystem.h:36
MusicMetadata * getCDMetadata(int m_the_track)
bool determineIfCompilation(bool cd=false)
int numRowsAffected() const
Definition: mythdbcon.h:206
bool operator!=(MusicMetadata &a, MusicMetadata &b)
static MusicMetadata * createFromFilename(const QString &filename)
#define ID_TO_ID(x)
Definition: musicmetadata.h:70
#define METADATA_INVALID_FILENAME
Definition: musicmetadata.h:73
ImageType
Definition: musicmetadata.h:28
Default UTC.
Definition: mythdate.h:14
void removeStream(MusicMetadata *mdata)
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:603
QString m_broadcaster
MythDB * GetMythDB(void)
Definition: mythdb.cpp:46
bool isValidID(int an_id)
bool SaveSettingOnHost(const QString &key, const QString &newValue, const QString &host)
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:179
bool IsMasterBackend(void)
is this the actual MBE process
QString m_country
void setField(const QString &field, const QString &data)
void ensureSortFields(void)
static void setArtistAndTrackFormats()
QString Broadcaster(void)
QString GetHostName(void)
void addImage(const AlbumArtImage &newImage)
static QString s_formatNormalFileTrack
avoid blocking LIRC & Joystick Menu
Definition: mythsystem.h:34
int at(void) const
Definition: mythdbcon.h:210
void toMap(InfoMap &metadataMap, const QString &prefix="")
void setID(IdType lid)
void loadStreams(void)
std::shared_ptr< MythSortHelper > getMythSortHelper(void)
Get a pointer to the MythSortHelper singleton.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:41
void setEmbeddedAlbumArt(AlbumArtList &albumart)