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