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