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