MythTV master
upnpcdsmusic.cpp
Go to the documentation of this file.
1
2// Program Name: upnpcdsmusic.cpp
3//
4// Purpose - uPnp Content Directory Extension for Music
5//
6// Created By : David Blain Created On : Jan. 24, 2005
7// Modified By : Modified On:
8//
10
11// C++
12#include <climits>
13
14// Qt
15#include <QFileInfo>
16#include <QUrl>
17#include <QUrlQuery>
18
19// MythTV
25
26// MythBackend
27#include "upnpcdsmusic.h"
28
65 : UPnpCDSExtension( QObject::tr("Music"), "Music",
66 "object.item.audioItem.musicTrack" )
67{
68 QString sServerIp = gCoreContext->GetBackendServerIP();
70 m_uriBase.setScheme("http");
71 m_uriBase.setHost(sServerIp);
72 m_uriBase.setPort(sPort);
73
74 // ShortCuts
76 m_shortcuts.insert(UPnPShortcutFeature::MUSIC_ALL, "Music/Track");
78 m_shortcuts.insert(UPnPShortcutFeature::MUSIC_ARTISTS, "Music/Artist");
80}
81
83//
85
87{
88 if (m_pRoot)
89 return;
90
92 m_sName,
93 "0");
94
95 QString containerId = m_sExtensionId + "/%1";
96
97 // HACK: I'm not entirely happy with this solution, but it's at least
98 // tidier than passing through half a dozen extra args to Load[Foo]
99 // or having yet more methods just to load the counts
100 auto *pRequest = new UPnpCDSRequest();
101 pRequest->m_nRequestedCount = 0; // We don't want to load any results, we just want the TotalCount
102 auto *pResult = new UPnpCDSExtensionResults();
103 IDTokenMap tokens;
104 // END HACK
105
106 // -----------------------------------------------------------------------
107 // All Tracks
108 // -----------------------------------------------------------------------
109 CDSObject* pContainer = CDSObject::CreateContainer ( containerId.arg("Track"),
110 QObject::tr("All Tracks"),
111 m_sExtensionId, // Parent Id
112 nullptr );
113 // HACK
114 LoadTracks(pRequest, pResult, tokens);
115 pContainer->SetChildCount(pResult->m_nTotalMatches);
116 pContainer->SetChildContainerCount(0);
117 // END HACK
118 m_pRoot->AddChild(pContainer);
119
120 // -----------------------------------------------------------------------
121 // By Artist
122 // -----------------------------------------------------------------------
123 pContainer = CDSObject::CreateContainer ( containerId.arg("Artist"),
124 QObject::tr("Artist"),
125 m_sExtensionId, // Parent Id
126 nullptr );
127 // HACK
128 LoadArtists(pRequest, pResult, tokens);
129 pContainer->SetChildCount(pResult->m_nTotalMatches);
130 pContainer->SetChildContainerCount(pResult->m_nTotalMatches);
131 // END HACK
132 m_pRoot->AddChild(pContainer);
133
134 // -----------------------------------------------------------------------
135 // By Album
136 // -----------------------------------------------------------------------
137 pContainer = CDSObject::CreateContainer ( containerId.arg("Album"),
138 QObject::tr("Album"),
139 m_sExtensionId, // Parent Id
140 nullptr );
141 // HACK
142 LoadAlbums(pRequest, pResult, tokens);
143 pContainer->SetChildCount(pResult->m_nTotalMatches);
144 pContainer->SetChildContainerCount(pResult->m_nTotalMatches);
145 // END HACK
146 m_pRoot->AddChild(pContainer);
147
148 // -----------------------------------------------------------------------
149 // By Genre
150 // -----------------------------------------------------------------------
151 pContainer = CDSObject::CreateContainer ( containerId.arg("Genre"),
152 QObject::tr("Genre"),
153 m_sExtensionId, // Parent Id
154 nullptr );
155 // HACK
156 LoadGenres(pRequest, pResult, tokens);
157 pContainer->SetChildCount(pResult->m_nTotalMatches);
158 pContainer->SetChildContainerCount(pResult->m_nTotalMatches);
159 // END HACK
160 m_pRoot->AddChild(pContainer);
161
162 // -----------------------------------------------------------------------
163 // By Directory
164 // -----------------------------------------------------------------------
165// pContainer = CDSObject::CreateStorageSystem ( containerId.arg("Directory"),
166// QObject::tr("Directory"),
167// m_sExtensionId, // Parent Id
168// nullptr );
169// // HACK
170// LoadDirectories(pRequest, pResult, tokens);
171// pContainer->SetChildCount(pResult->m_nTotalMatches);
172// pContainer->SetChildContainerCount(pResult->m_nTotalMatches);
173// // END HACK
174// m_pRoot->AddChild(pContainer);
175
176 // -----------------------------------------------------------------------
177
178 // HACK
179 delete pRequest;
180 delete pResult;
181 // END HACK
182}
183
185//
187
189{
190 // ----------------------------------------------------------------------
191 // See if we need to modify the request for compatibility
192 // ----------------------------------------------------------------------
193
194 // Xbox360 compatibility code.
195
196// if (pRequest->m_eClient == CDS_ClientXBox &&
197// pRequest->m_sContainerID == "7")
198// {
199// pRequest->m_sObjectId = "Music";
200//
201// LOG(VB_UPNP, LOG_INFO,
202// "UPnpCDSMusic::IsBrowseRequestForUs - Yes, ContainerId == 7");
203//
204// return true;
205// }
206//
207// if ((pRequest->m_sObjectId.isEmpty()) &&
208// (!pRequest->m_sContainerID.isEmpty()))
209// pRequest->m_sObjectId = pRequest->m_sContainerID;
210
211 LOG(VB_UPNP, LOG_INFO,
212 "UPnpCDSMusic::IsBrowseRequestForUs - Not sure... Calling base class.");
213
215}
216
218//
220
222{
223 // ----------------------------------------------------------------------
224 // See if we need to modify the request for compatibility
225 // ----------------------------------------------------------------------
226
227 // XBox 360 compatibility code
228
229// if (pRequest->m_eClient == CDS_ClientXBox &&
230// pRequest->m_sContainerID == "7")
231// {
232// pRequest->m_sObjectId = "Music/1";
233// pRequest->m_sSearchCriteria = "object.container.album.musicAlbum";
234// pRequest->m_sSearchList.append( pRequest->m_sSearchCriteria );
235//
236// LOG(VB_UPNP, LOG_INFO, "UPnpCDSMusic::IsSearchRequestForUs... Yes.");
237//
238// return true;
239// }
240//
241// if (pRequest->m_sContainerID == "4")
242// {
243// pRequest->m_sObjectId = "Music";
244// pRequest->m_sSearchCriteria = "object.item.audioItem.musicTrack";
245// pRequest->m_sSearchList.append( pRequest->m_sSearchCriteria );
246//
247// LOG(VB_UPNP, LOG_INFO, "UPnpCDSMusic::IsSearchRequestForUs... Yes.");
248//
249// return true;
250// }
251//
252// if ((pRequest->m_sObjectId.isEmpty()) &&
253// (!pRequest->m_sContainerID.isEmpty()))
254// pRequest->m_sObjectId = pRequest->m_sContainerID;
255
256 LOG(VB_UPNP, LOG_INFO,
257 "UPnpCDSMusic::IsSearchRequestForUs.. Don't know, calling base class.");
258
260}
261
263//
265
267 UPnpCDSExtensionResults* pResults,
268 const IDTokenMap& tokens, const QString& currentToken)
269{
270 if (currentToken.isEmpty())
271 {
272 LOG(VB_GENERAL, LOG_ERR, QString("UPnpCDSMusic::LoadMetadata: Final "
273 "token missing from id: %1")
274 .arg(pRequest->m_sParentId));
275 return false;
276 }
277
278 // Root or Root + 1
279 if (tokens[currentToken].isEmpty())
280 {
281 CDSObject *container = nullptr;
282
283 if (pRequest->m_sObjectId == m_sExtensionId)
284 container = GetRoot();
285 else
286 container = GetRoot()->GetChild(pRequest->m_sObjectId);
287
288 if (container)
289 {
290 pResults->Add(container);
291 pResults->m_nTotalMatches = 1;
292 return true;
293 }
294
295 LOG(VB_GENERAL, LOG_ERR, QString("UPnpCDSMusic::LoadMetadata: Requested "
296 "object cannot be found: %1")
297 .arg(pRequest->m_sObjectId));
298 return false;
299 }
300 if (currentToken == "genre")
301 {
302 // Genre is presently a top tier node, since it doesn't appear
303 // below Artist/Album/etc we don't need to pass through
304 // the ids for filtering
305 return LoadGenres(pRequest, pResults, tokens);
306 }
307 if (currentToken == "artist")
308 {
309 return LoadArtists(pRequest, pResults, tokens);
310 }
311 if (currentToken == "album")
312 {
313 return LoadAlbums(pRequest, pResults, tokens);
314 }
315 if (currentToken == "track")
316 {
317 return LoadTracks(pRequest, pResults, tokens);
318 }
319
320 LOG(VB_GENERAL, LOG_ERR,
321 QString("UPnpCDSMusic::LoadMetadata(): "
322 "Unhandled metadata request for '%1'.").arg(currentToken));
323 return false;
324}
325
327//
329
331 UPnpCDSExtensionResults* pResults,
332 const IDTokenMap& tokens, const QString& currentToken)
333{
334 if (currentToken.isEmpty() || currentToken == m_sExtensionId.toLower())
335 {
336 // Root
337 pResults->Add(GetRoot()->GetChildren());
338 pResults->m_nTotalMatches = GetRoot()->GetChildCount();
339 return true;
340 }
341 if (currentToken == "track")
342 {
343 return LoadTracks(pRequest, pResults, tokens);
344 }
345 if (currentToken == "genre")
346 {
347 if (tokens["genre"].toInt() > 0)
348 return LoadArtists(pRequest, pResults, tokens);
349 return LoadGenres(pRequest, pResults, tokens);
350 }
351 if (currentToken == "artist")
352 {
353 if (tokens["artist"].toInt() > 0)
354 return LoadAlbums(pRequest, pResults, tokens);
355 return LoadArtists(pRequest, pResults, tokens);
356 }
357 if (currentToken == "album")
358 {
359 if (tokens["album"].toInt() > 0)
360 return LoadTracks(pRequest, pResults, tokens);
361 return LoadAlbums(pRequest, pResults, tokens);
362 }
363 LOG(VB_GENERAL, LOG_ERR,
364 QString("UPnpCDSMusic::LoadChildren(): "
365 "Unhandled metadata request for '%1'.").arg(currentToken));
366
367 return false;
368}
369
371//
373
375{
376 QUrl artURI = m_uriBase;
377 artURI.setPath("/Content/GetAlbumArt");
378 QUrlQuery artQuery;
379 artQuery.addQueryItem("Id", QString::number(nSongID));
380 artURI.setQuery(artQuery);
381
382 QList<Property*> propList = pItem->GetProperties("albumArtURI");
383 if (propList.size() >= 4)
384 {
385 // Prefer JPEG over PNG here, although PNG is allowed DLNA requires JPEG
386 // and crucially the filesizes are smaller which speeds up loading times
387 // over the network
388
389 // We MUST include the thumbnail size, but since some clients may use the
390 // first image they see and the thumbnail is tiny, instead return the
391 // medium first. The large could be very large, which is no good if the
392 // client is pulling images for an entire list at once!
393
394 // Medium
395 Property *pProp = propList.at(0);
396 if (pProp)
397 {
398 // Must be no more than 1024x768
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());
405 pProp->AddAttribute("dlna:profileID", "JPEG_MED");
406 pProp->AddAttribute("xmlns:dlna", "urn:schemas-dlna-org:metadata-1-0");
407 }
408
409 // Thumbnail
410 pProp = propList.at(1);
411 if (pProp)
412 {
413 // At least one albumArtURI must be a ThumbNail (TN) no larger
414 // than 160x160, and it must also be a jpeg
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());
421 pProp->AddAttribute("dlna:profileID", "JPEG_TN");
422 pProp->AddAttribute("xmlns:dlna", "urn:schemas-dlna-org:metadata-1-0");
423 }
424
425 // Small
426 pProp = propList.at(2);
427 if (pProp)
428 {
429 // Must be no more than 640x480
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());
436 pProp->AddAttribute("dlna:profileID", "JPEG_SM");
437 pProp->AddAttribute("xmlns:dlna", "urn:schemas-dlna-org:metadata-1-0");
438 }
439
440 // Large
441 pProp = propList.at(3);
442 if (pProp)
443 {
444 // Must be no more than 4096x4096 - for our purposes, just return
445 // a fullsize image
446 pProp->SetValue(artURI.toEncoded());
447 pProp->AddAttribute("dlna:profileID", "JPEG_LRG");
448 pProp->AddAttribute("xmlns:dlna", "urn:schemas-dlna-org:metadata-1-0");
449 }
450 }
451 else
452 {
453 LOG(VB_GENERAL, LOG_ERR, QString("Unable to designate album artwork "
454 "for '%1' with class '%2' and id '%3'")
455 .arg(pItem->m_sId,
456 pItem->m_sClass,
457 QString::number(nSongID)));
458 }
459}
460
462//
464
466 UPnpCDSExtensionResults *pResults,
467 const IDTokenMap& tokens)
468{
469 QString sRequestId = pRequest->m_sObjectId;
470
471 uint16_t nCount = pRequest->m_nRequestedCount;
472 uint16_t nOffset = pRequest->m_nStartingIndex;
473
474 // We must use a dedicated connection to get an acccurate value from
475 // FOUND_ROWS()
477
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 "
487 "%1 " // WHERE clauses
488 "GROUP BY a.album_id "
489 "ORDER BY a.album_name "
490 "LIMIT :OFFSET,:COUNT";
491
492 QStringList clauses;
493 QString whereString = BuildWhereClause(clauses, tokens);
494
495 query.prepare(sql.arg(whereString));
496
497 BindValues(query, tokens);
498
499 query.bindValue(":OFFSET", nOffset);
500 query.bindValue(":COUNT", nCount);
501
502 if (!query.exec())
503 return false;
504
505 while (query.next())
506 {
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(); // TODO: Allow artwork lookups by album ID
513 QString sGenre = query.value(6).toString();
514 int nTrackCount = query.value(7).toInt();
515 int nAlbumArtID = query.value(8).toInt();
516
517 CDSObject* pContainer = CDSObject::CreateMusicAlbum( CreateIDString(sRequestId, "Album", nAlbumID),
518 sAlbumName,
519 pRequest->m_sParentId,
520 nullptr );
521 pContainer->SetPropValue("artist", sArtist);
522 pContainer->SetPropValue("date", sYear);
523 pContainer->SetPropValue("genre", sGenre);
524 pContainer->SetChildCount(nTrackCount);
525 pContainer->SetChildContainerCount(0);
526 if (bCompilation)
527 {
528 // Do nothing for now
529 }
530
531 // Artwork
532
533 if (nAlbumArtID > 0)
534 PopulateArtworkURIS(pContainer, nSongId);
535
536 pResults->Add(pContainer);
537 pContainer->DecrRef();
538 }
539
540 // Just in case FOUND_ROWS() should fail, ensure m_nTotalMatches contains
541 // at least the size of this result set
542 if (query.size() > 0)
543 pResults->m_nTotalMatches = query.size();
544
545 // Fetch the total number of matches ignoring any LIMITs
546 query.prepare("SELECT FOUND_ROWS()");
547 if (query.exec() && query.next())
548 pResults->m_nTotalMatches = query.value(0).toUInt();
549
550 return true;
551}
552
554//
556
558 UPnpCDSExtensionResults *pResults,
559 const IDTokenMap& tokens)
560{
561 QString sRequestId = pRequest->m_sObjectId;
562
563 uint16_t nCount = pRequest->m_nRequestedCount;
564 uint16_t nOffset = pRequest->m_nStartingIndex;
565
566 // We must use a dedicated connection to get an accurate value from
567 // FOUND_ROWS()
569
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 "
577 "%1 " // WHERE clauses
578 "GROUP BY t.artist_id "
579 "ORDER BY t.artist_name "
580 "LIMIT :OFFSET,:COUNT";
581
582
583 QStringList clauses;
584 QString whereString = BuildWhereClause(clauses, tokens);
585
586 query.prepare(sql.arg(whereString));
587
588 BindValues(query, tokens);
589
590 query.bindValue(":OFFSET", nOffset);
591 query.bindValue(":COUNT", nCount);
592
593 if (!query.exec())
594 return false;
595
596 while (query.next())
597 {
598 int nArtistId = query.value(0).toInt();
599 QString sArtistName = query.value(1).toString();
600// QStringList sGenres = query.value(2).toString().split(',');
601 int nAlbumCount = query.value(3).toInt();
602
603 CDSObject* pContainer = CDSObject::CreateMusicArtist( CreateIDString(sRequestId, "Artist", nArtistId),
604 sArtistName,
605 pRequest->m_sParentId,
606 nullptr );
607// TODO: Add SetPropValues option for multi-value properties
608// QStringList::Iterator it;
609// for (it = sGenres.begin(); it != sGenres.end(); ++it)
610// {
611// pContainer->SetPropValues("genre", sGenres);
612// }
613 pContainer->SetChildCount(nAlbumCount);
614 pContainer->SetChildContainerCount(nAlbumCount);
615
616 // Artwork
617 //PopulateArtistURIS(pContainer, nArtistId);
618
619 pResults->Add(pContainer);
620 pContainer->DecrRef();
621 }
622
623 // Just in case FOUND_ROWS() should fail, ensure m_nTotalMatches contains
624 // at least the size of this result set
625 if (query.size() > 0)
626 pResults->m_nTotalMatches = query.size();
627
628 // Fetch the total number of matches ignoring any LIMITs
629 query.prepare("SELECT FOUND_ROWS()");
630 if (query.exec() && query.next())
631 pResults->m_nTotalMatches = query.value(0).toUInt();
632
633 return true;
634}
635
637//
639
641 UPnpCDSExtensionResults *pResults,
642 const IDTokenMap& tokens )
643{
644 QString sRequestId = pRequest->m_sObjectId;
645
646 uint16_t nCount = pRequest->m_nRequestedCount;
647 uint16_t nOffset = pRequest->m_nStartingIndex;
648
649 // We must use a dedicated connection to get an acccurate value from
650 // FOUND_ROWS()
652
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 "
658 "%1 " // WHERE clauses
659 "GROUP BY g.genre_id "
660 "ORDER BY g.genre "
661 "LIMIT :OFFSET,:COUNT";
662
663 QStringList clauses;
664 QString whereString = BuildWhereClause(clauses, tokens);
665
666 query.prepare(sql.arg(whereString));
667
668 BindValues(query, tokens);
669
670 query.bindValue(":OFFSET", nOffset);
671 query.bindValue(":COUNT", nCount);
672
673 if (!query.exec())
674 return false;
675
676 while (query.next())
677 {
678 int nGenreId = query.value(0).toInt();
679 QString sGenreName = query.value(1).toString();
680 int nArtistCount = query.value(2).toInt();
681
682 CDSObject* pContainer = CDSObject::CreateMusicGenre( CreateIDString(sRequestId, "Genre", nGenreId),
683 sGenreName,
684 pRequest->m_sParentId,
685 nullptr );
686 pContainer->SetPropValue("description", sGenreName);
687
688 pContainer->SetChildCount(nArtistCount);
689 pContainer->SetChildContainerCount(nArtistCount);
690
691 pResults->Add(pContainer);
692 pContainer->DecrRef();
693 }
694
695 // Just in case FOUND_ROWS() should fail, ensure m_nTotalMatches contains
696 // at least the size of this result set
697 if (query.size() > 0)
698 pResults->m_nTotalMatches = query.size();
699
700 // Fetch the total number of matches ignoring any LIMITs
701 query.prepare("SELECT FOUND_ROWS()");
702 if (query.exec() && query.next())
703 pResults->m_nTotalMatches = query.value(0).toUInt();
704
705 return true;
706}
707
709//
711
713 UPnpCDSExtensionResults *pResults,
714 const IDTokenMap& tokens)
715{
716 QString sRequestId = pRequest->m_sObjectId;
717
718 uint16_t nCount = pRequest->m_nRequestedCount;
719 uint16_t nOffset = pRequest->m_nStartingIndex;
720
721 // We must use a dedicated connection to get an acccurate value from
722 // FOUND_ROWS()
724
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 "
735 "%1 " // WHERE clauses
736 "GROUP BY s.song_id "
737 "ORDER BY t.artist_name, a.album_name, s.track "
738 "LIMIT :OFFSET,:COUNT";
739
740 QStringList clauses;
741 QString whereString = BuildWhereClause(clauses, tokens);
742
743 query.prepare(sql.arg(whereString));
744
745 BindValues(query, tokens);
746
747 query.bindValue(":OFFSET", nOffset);
748 query.bindValue(":COUNT", nCount);
749
750 if (!query.exec())
751 return false;
752
753 while (query.next())
754 {
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();
766
767 int nPlaybackCount = query.value(11).toInt();
768 QDateTime lastPlayedTime = query.value(12).toDateTime();
769 int nAlbumArtID = query.value(13).toInt();
770
771 CDSObject* pItem = CDSObject::CreateMusicTrack( CreateIDString(sRequestId, "Track", nId),
772 sTitle,
773 pRequest->m_sParentId,
774 nullptr );
775
776 // Only add the reference ID for items which are not in the
777 // 'All Tracks' container
778 QString sRefIDBase = QString("%1/Track").arg(m_sExtensionId);
779 if ( pRequest->m_sParentId != sRefIDBase )
780 {
781 QString sRefId = QString( "%1=%2")
782 .arg( sRefIDBase )
783 .arg( nId );
784
785 pItem->SetPropValue( "refID", sRefId );
786 }
787
788 pItem->SetPropValue( "genre" , sGenre );
789 pItem->SetPropValue( "description" , sTitle );
790 pItem->SetPropValue( "longDescription" , sDescription);
791
792 pItem->SetPropValue( "artist" , sArtist );
793 pItem->SetPropValue( "creator" , sArtist );
794 pItem->SetPropValue( "album" , sAlbum );
795 pItem->SetPropValue( "originalTrackNumber" , QString::number(nTrackNum));
796 if (nYear > 0 && nYear < 9999)
797 pItem->SetPropValue( "date", QDate(nYear,1,1).toString(Qt::ISODate));
798
799 pItem->SetPropValue( "playbackCount" , QString::number(nPlaybackCount));
800 pItem->SetPropValue( "lastPlaybackTime" , UPnPDateTime::DateTimeFormat(lastPlayedTime));
801
802 // Artwork
803 if (nAlbumArtID > 0)
804 PopulateArtworkURIS(pItem, nId);
805
806 // ----------------------------------------------------------------------
807 // Add Music Resource Element based on File extension (HTTP)
808 // ----------------------------------------------------------------------
809
810 QFileInfo fInfo( sFileName );
811
812 QUrl resURI = m_uriBase;
813 QUrlQuery resQuery;
814 resURI.setPath("/Content/GetMusic");
815 resQuery.addQueryItem("Id", QString::number(nId));
816 resURI.setQuery(resQuery);
817
818 QString sMimeType = HTTPRequest::GetMimeType( fInfo.suffix() );
819
821 sMimeType);
822
823 Resource *pResource = pItem->AddResource( sProtocol, resURI.toEncoded() );
824
825 pResource->AddAttribute( "duration" , UPnPDateTime::resDurationFormat(nLengthMS) );
826 if (nFileSize > 0)
827 pResource->AddAttribute( "size" , QString::number( nFileSize) );
828
829 pResults->Add(pItem);
830 pItem->DecrRef();
831 }
832
833 // Just in case FOUND_ROWS() should fail, ensure m_nTotalMatches contains
834 // at least the size of this result set
835 if (query.size() > 0)
836 pResults->m_nTotalMatches = query.size();
837
838 // Fetch the total number of matches ignoring any LIMITs
839 query.prepare("SELECT FOUND_ROWS()");
840 if (query.exec() && query.next())
841 pResults->m_nTotalMatches = query.value(0).toUInt();
842
843 return true;
844}
845
847//
849
850QString UPnpCDSMusic::BuildWhereClause( QStringList clauses,
851 IDTokenMap tokens)
852{
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");
865
866// if (tokens["album"].toInt() > 0)
867// clauses.append("a.album_id=:ALBUM_ID");
868// if (tokens["artist"].toInt() > 0)
869// clauses.append("a.artist_id=:ARTIST_ID");
870
871 QString whereString;
872 if (!clauses.isEmpty())
873 {
874 whereString = " WHERE ";
875 whereString.append(clauses.join(" AND "));
876 }
877
878 return whereString;
879}
880
882//
884
886 IDTokenMap tokens)
887{
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"]);
900}
901
902// vim:ts=4:sw=4:ai:et:si:sts=4
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)
QString m_sClass
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)
QString m_sId
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.
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
@ kDedicatedConnection
Definition: mythdbcon.h:228
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
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
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)
Definition: upnpcds.cpp:32
uint16_t m_nTotalMatches
Definition: upnpcds.h:114
QString m_sExtensionId
Definition: upnpcds.h:207
virtual CDSObject * GetRoot()
Definition: upnpcds.cpp:1086
QString m_sName
Definition: upnpcds.h:208
static QString CreateIDString(const QString &RequestId, const QString &Name, int Value)
Definition: upnpcds.cpp:1053
CDSObject * m_pRoot
Definition: upnpcds.h:249
virtual bool IsBrowseRequestForUs(UPnpCDSRequest *pRequest)
Definition: upnpcds.cpp:805
virtual bool IsSearchRequestForUs(UPnpCDSRequest *pRequest)
Definition: upnpcds.cpp:892
CDSShortCutList m_shortcuts
Definition: upnpcds.h:211
bool LoadMetadata(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens, const QString &currentToken) 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 &currentToken) 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
Definition: upnpcds.h:81
QString m_sObjectId
Definition: upnpcds.h:76
uint16_t m_nStartingIndex
Definition: upnpcds.h:80
QString m_sParentId
Definition: upnpcds.h:86
unsigned short uint16_t
Definition: iso6937tables.h:3
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
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.
Definition: mythdate.cpp:93
@ ISODate
Default UTC.
Definition: mythdate.h:17
QString resDurationFormat(std::chrono::milliseconds msec)
res@duration Format B.2.1.4 res@duration - UPnP ContentDirectory Service 2008, 2013
Definition: upnphelpers.cpp:81
QString DateTimeFormat(const QDateTime &dateTime)
Date-Time Format.
Definition: upnphelpers.cpp:43
QMap< QString, QString > IDTokenMap
Definition: upnpcds.h:200