66 "object.item.audioItem.musicTrack" )
101 pRequest->m_nRequestedCount = 0;
110 QObject::tr(
"All Tracks"),
124 QObject::tr(
"Artist"),
138 QObject::tr(
"Album"),
152 QObject::tr(
"Genre"),
211 LOG(VB_UPNP, LOG_INFO,
212 "UPnpCDSMusic::IsBrowseRequestForUs - Not sure... Calling base class.");
256 LOG(VB_UPNP, LOG_INFO,
257 "UPnpCDSMusic::IsSearchRequestForUs.. Don't know, calling base class.");
268 const IDTokenMap& tokens,
const QString& currentToken)
270 if (currentToken.isEmpty())
272 LOG(VB_GENERAL, LOG_ERR, QString(
"UPnpCDSMusic::LoadMetadata: Final "
273 "token missing from id: %1")
279 if (tokens[currentToken].isEmpty())
290 pResults->
Add(container);
295 LOG(VB_GENERAL, LOG_ERR, QString(
"UPnpCDSMusic::LoadMetadata: Requested "
296 "object cannot be found: %1")
300 if (currentToken ==
"genre")
305 return LoadGenres(pRequest, pResults, tokens);
307 if (currentToken ==
"artist")
311 if (currentToken ==
"album")
313 return LoadAlbums(pRequest, pResults, tokens);
315 if (currentToken ==
"track")
317 return LoadTracks(pRequest, pResults, tokens);
320 LOG(VB_GENERAL, LOG_ERR,
321 QString(
"UPnpCDSMusic::LoadMetadata(): "
322 "Unhandled metadata request for '%1'.").arg(currentToken));
332 const IDTokenMap& tokens,
const QString& currentToken)
334 if (currentToken.isEmpty() || currentToken ==
m_sExtensionId.toLower())
341 if (currentToken ==
"track")
343 return LoadTracks(pRequest, pResults, tokens);
345 if (currentToken ==
"genre")
347 if (tokens[
"genre"].toInt() > 0)
349 return LoadGenres(pRequest, pResults, tokens);
351 if (currentToken ==
"artist")
353 if (tokens[
"artist"].toInt() > 0)
354 return LoadAlbums(pRequest, pResults, tokens);
357 if (currentToken ==
"album")
359 if (tokens[
"album"].toInt() > 0)
360 return LoadTracks(pRequest, pResults, tokens);
361 return LoadAlbums(pRequest, pResults, tokens);
363 LOG(VB_GENERAL, LOG_ERR,
364 QString(
"UPnpCDSMusic::LoadChildren(): "
365 "Unhandled metadata request for '%1'.").arg(currentToken));
377 artURI.setPath(
"/Content/GetAlbumArt");
379 artQuery.addQueryItem(
"Id", QString::number(nSongID));
380 artURI.setQuery(artQuery);
382 QList<Property*> propList = pItem->
GetProperties(
"albumArtURI");
383 if (propList.size() >= 4)
399 QUrl mediumURI = artURI;
400 QUrlQuery mediumQuery(mediumURI.query());
401 mediumQuery.addQueryItem(
"Width",
"1024");
402 mediumQuery.addQueryItem(
"Height",
"768");
403 mediumURI.setQuery(mediumQuery);
404 pProp->
SetValue(mediumURI.toEncoded());
406 pProp->
AddAttribute(
"xmlns:dlna",
"urn:schemas-dlna-org:metadata-1-0");
410 pProp = propList.at(1);
415 QUrl thumbURI = artURI;
416 QUrlQuery thumbQuery(thumbURI.query());
417 thumbQuery.addQueryItem(
"Width",
"160");
418 thumbQuery.addQueryItem(
"Height",
"160");
419 thumbURI.setQuery(thumbQuery);
420 pProp->
SetValue(thumbURI.toEncoded());
422 pProp->
AddAttribute(
"xmlns:dlna",
"urn:schemas-dlna-org:metadata-1-0");
426 pProp = propList.at(2);
430 QUrl smallURI = artURI;
431 QUrlQuery smallQuery(smallURI.query());
432 smallQuery.addQueryItem(
"Width",
"640");
433 smallQuery.addQueryItem(
"Height",
"480");
434 smallURI.setQuery(smallQuery);
435 pProp->
SetValue(smallURI.toEncoded());
437 pProp->
AddAttribute(
"xmlns:dlna",
"urn:schemas-dlna-org:metadata-1-0");
441 pProp = propList.at(3);
446 pProp->
SetValue(artURI.toEncoded());
448 pProp->
AddAttribute(
"xmlns:dlna",
"urn:schemas-dlna-org:metadata-1-0");
453 LOG(VB_GENERAL, LOG_ERR, QString(
"Unable to designate album artwork "
454 "for '%1' with class '%2' and id '%3'")
457 QString::number(nSongID)));
478 QString sql =
"SELECT SQL_CALC_FOUND_ROWS "
479 "a.album_id, a.album_name, t.artist_name, a.year, "
480 "a.compilation, s.song_id, g.genre, "
481 "COUNT(a.album_id), w.albumart_id "
482 "FROM music_albums a "
483 "LEFT JOIN music_artists t ON a.artist_id=t.artist_id "
484 "LEFT JOIN music_songs s ON a.album_id=s.album_id "
485 "LEFT JOIN music_genres g ON s.genre_id=g.genre_id "
486 "LEFT JOIN music_albumart w ON s.song_id=w.song_id "
488 "GROUP BY a.album_id "
489 "ORDER BY a.album_name "
490 "LIMIT :OFFSET,:COUNT";
495 query.
prepare(sql.arg(whereString));
507 int nAlbumID = query.
value(0).toInt();
508 QString sAlbumName = query.
value(1).toString();
509 QString sArtist = query.
value(2).toString();
510 QString sYear = query.
value(3).toString();
511 bool bCompilation = query.
value(4).toBool();
512 int nSongId = query.
value(5).toInt();
513 QString sGenre = query.
value(6).toString();
514 int nTrackCount = query.
value(7).toInt();
515 int nAlbumArtID = query.
value(8).toInt();
536 pResults->
Add(pContainer);
542 if (query.
size() > 0)
546 query.
prepare(
"SELECT FOUND_ROWS()");
570 QString sql =
"SELECT SQL_CALC_FOUND_ROWS "
571 "t.artist_id, t.artist_name, CONCAT_WS(',', g.genre), "
572 "COUNT(DISTINCT a.album_id) "
573 "FROM music_artists t "
574 "LEFT JOIN music_albums a ON a.artist_id = t.artist_id "
575 "JOIN music_songs s ON t.artist_id = s.artist_id "
576 "LEFT JOIN music_genres g ON s.genre_id = g.genre_id "
578 "GROUP BY t.artist_id "
579 "ORDER BY t.artist_name "
580 "LIMIT :OFFSET,:COUNT";
586 query.
prepare(sql.arg(whereString));
598 int nArtistId = query.
value(0).toInt();
599 QString sArtistName = query.
value(1).toString();
601 int nAlbumCount = query.
value(3).toInt();
619 pResults->
Add(pContainer);
625 if (query.
size() > 0)
629 query.
prepare(
"SELECT FOUND_ROWS()");
653 QString sql =
"SELECT SQL_CALC_FOUND_ROWS g.genre_id, g.genre, "
654 "COUNT( DISTINCT t.artist_id ) "
655 "FROM music_genres g "
656 "LEFT JOIN music_songs s ON g.genre_id = s.genre_id "
657 "LEFT JOIN music_artists t ON t.artist_id = s.artist_id "
659 "GROUP BY g.genre_id "
661 "LIMIT :OFFSET,:COUNT";
666 query.
prepare(sql.arg(whereString));
678 int nGenreId = query.
value(0).toInt();
679 QString sGenreName = query.
value(1).toString();
680 int nArtistCount = query.
value(2).toInt();
691 pResults->
Add(pContainer);
697 if (query.
size() > 0)
701 query.
prepare(
"SELECT FOUND_ROWS()");
725 QString sql =
"SELECT SQL_CALC_FOUND_ROWS s.song_id, t.artist_name, "
726 "a.album_name, s.name, "
727 "g.genre, s.year, s.track, "
728 "s.description, s.filename, s.length, s.size, "
729 "s.numplays, s.lastplay, w.albumart_id "
730 "FROM music_songs s "
731 "LEFT JOIN music_artists t ON t.artist_id = s.artist_id "
732 "LEFT JOIN music_albums a ON a.album_id = s.album_id "
733 "LEFT JOIN music_genres g ON g.genre_id = s.genre_id "
734 "LEFT JOIN music_albumart w ON s.song_id = w.song_id "
736 "GROUP BY s.song_id "
737 "ORDER BY t.artist_name, a.album_name, s.track "
738 "LIMIT :OFFSET,:COUNT";
743 query.
prepare(sql.arg(whereString));
755 int nId = query.
value( 0).toInt();
756 QString sArtist = query.
value( 1).toString();
757 QString sAlbum = query.
value( 2).toString();
758 QString sTitle = query.
value( 3).toString();
759 QString sGenre = query.
value( 4).toString();
760 int nYear = query.
value( 5).toInt();
761 int nTrackNum = query.
value( 6).toInt();
762 QString sDescription = query.
value( 7).toString();
763 QString sFileName = query.
value( 8).toString();
764 auto nLengthMS = std::chrono::milliseconds(query.
value( 9).toUInt());
765 uint64_t nFileSize = query.
value(10).toULongLong();
767 int nPlaybackCount = query.
value(11).toInt();
768 QDateTime lastPlayedTime = query.
value(12).toDateTime();
769 int nAlbumArtID = query.
value(13).toInt();
781 QString sRefId = QString(
"%1=%2")
795 pItem->
SetPropValue(
"originalTrackNumber" , QString::number(nTrackNum));
796 if (nYear > 0 && nYear < 9999)
799 pItem->
SetPropValue(
"playbackCount" , QString::number(nPlaybackCount));
810 QFileInfo fInfo( sFileName );
814 resURI.setPath(
"/Content/GetMusic");
815 resQuery.addQueryItem(
"Id", QString::number(nId));
816 resURI.setQuery(resQuery);
827 pResource->
AddAttribute(
"size" , QString::number( nFileSize) );
829 pResults->
Add(pItem);
835 if (query.
size() > 0)
839 query.
prepare(
"SELECT FOUND_ROWS()");
853 if (tokens[
"track"].toInt() > 0)
854 clauses.append(
"s.song_id=:TRACK_ID");
855 if (tokens[
"album"].toInt() > 0)
856 clauses.append(
"s.album_id=:ALBUM_ID");
857 if (tokens[
"artist"].toInt() > 0)
858 clauses.append(
"s.artist_id=:ARTIST_ID");
859 if (tokens[
"genre"].toInt() > 0)
860 clauses.append(
"s.genre_id=:GENRE_ID");
861 if (tokens[
"year"].toInt() > 0)
862 clauses.append(
"s.year=:YEAR");
863 if (tokens[
"directory"].toInt() > 0)
864 clauses.append(
"s.directory_id=:DIRECTORY_ID");
872 if (!clauses.isEmpty())
874 whereString =
" WHERE ";
875 whereString.append(clauses.join(
" AND "));
888 if (tokens[
"track"].toInt() > 0)
889 query.
bindValue(
":TRACK_ID", tokens[
"track"]);
890 if (tokens[
"album"].toInt() > 0)
891 query.
bindValue(
":ALBUM_ID", tokens[
"album"]);
892 if (tokens[
"artist"].toInt() > 0)
893 query.
bindValue(
":ARTIST_ID", tokens[
"artist"]);
894 if (tokens[
"genre"].toInt() > 0)
895 query.
bindValue(
":GENRE_ID", tokens[
"genre"]);
896 if (tokens[
"year"].toInt() > 0)
897 query.
bindValue(
":YEAR", tokens[
"year"]);
898 if (tokens[
"directory"].toInt() > 0)
899 query.
bindValue(
":DIRECTORY_ID", tokens[
"directory"]);
static CDSObject * CreateContainer(const QString &sId, const QString &sTitle, const QString &sParentId, CDSObject *pObject=nullptr)
uint32_t GetChildCount(void) const
Return the number of children in this container.
static CDSObject * CreateMusicArtist(const QString &sId, const QString &sTitle, const QString &sParentId, CDSObject *pObject=nullptr)
void SetChildCount(uint32_t nCount)
Allows the caller to set childCount without having to load children.
static CDSObject * CreateMusicTrack(const QString &sId, const QString &sTitle, const QString &sParentId, CDSObject *pObject=nullptr)
Resource * AddResource(const QString &sProtocol, const QString &sURI)
CDSObject * AddChild(CDSObject *pChild)
CDSObject * GetChild(const QString &sID)
void SetPropValue(const QString &sName, const QString &sValue, const QString &type="")
QList< Property * > GetProperties(const QString &sName)
void SetChildContainerCount(uint32_t nCount)
Allows the caller to set childContainerCount without having to load children.
static CDSObject * CreateMusicGenre(const QString &sId, const QString &sTitle, const QString &sParentId, CDSObject *pObject=nullptr)
static CDSObject * CreateMusicAlbum(const QString &sId, const QString &sTitle, const QString &sParentId, CDSObject *pObject=nullptr)
static QString GetMimeType(const QString &sFileExtension)
QSqlQuery wrapper that fetches a DB connection from the connection pool.
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
QVariant value(int i) const
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
int GetBackendStatusPort(void)
Returns the locally defined backend status port.
QString GetBackendServerIP(void)
Returns the IP address of the locally defined backend IP.
void AddAttribute(const QString &sName, const QString &sValue)
void SetValue(const QString &value)
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
void AddAttribute(const QString &sName, const QString &sValue)
void Add(CDSObject *pObject)
virtual CDSObject * GetRoot()
static QString CreateIDString(const QString &RequestId, const QString &Name, int Value)
virtual bool IsBrowseRequestForUs(UPnpCDSRequest *pRequest)
virtual bool IsSearchRequestForUs(UPnpCDSRequest *pRequest)
CDSShortCutList m_shortcuts
bool LoadMetadata(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens, const QString ¤tToken) override
Fetch just the metadata for the item identified in the request.
static bool LoadArtists(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens)
UPnpCDSMusic()
Music Extension for UPnP ContentDirectory Service.
void CreateRoot() override
bool LoadChildren(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens, const QString ¤tToken) override
Fetch the children of the container identified in the request.
static QString BuildWhereClause(QStringList clauses, IDTokenMap tokens)
bool LoadTracks(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens)
void PopulateArtworkURIS(CDSObject *pItem, int songID)
static bool LoadGenres(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens)
static void BindValues(MSqlQuery &query, IDTokenMap tokens)
bool LoadAlbums(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens)
bool IsBrowseRequestForUs(UPnpCDSRequest *pRequest) override
bool IsSearchRequestForUs(UPnpCDSRequest *pRequest) override
uint16_t m_nRequestedCount
uint16_t m_nStartingIndex
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
QString ProtocolInfoString(UPNPProtocol::TransferProtocol protocol, const QString &mimeType, const QSize resolution, double videoFrameRate, const QString &container, const QString &videoCodec, const QString &audioCodec, bool isTranscoded)
Create a properly formatted string for the 4th field of res@protocolInfo.
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
QString resDurationFormat(std::chrono::milliseconds msec)
res@duration Format B.2.1.4 res@duration - UPnP ContentDirectory Service 2008, 2013
QString DateTimeFormat(const QDateTime &dateTime)
Date-Time Format.
QMap< QString, QString > IDTokenMap