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