MythTV  master
upnpcdsmusic.cpp
Go to the documentation of this file.
1 // 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
24 
25 // MythBackend
26 #include "upnpcdsmusic.h"
27 
64  : UPnpCDSExtension( QObject::tr("Music"), "Music",
65  "object.item.audioItem.musicTrack" )
66 {
67  QString sServerIp = gCoreContext->GetBackendServerIP();
68  int sPort = gCoreContext->GetBackendStatusPort();
69  m_uriBase.setScheme("http");
70  m_uriBase.setHost(sServerIp);
71  m_uriBase.setPort(sPort);
72 
73  // ShortCuts
74  m_shortcuts.insert(UPnPShortcutFeature::MUSIC, "Music");
75  m_shortcuts.insert(UPnPShortcutFeature::MUSIC_ALL, "Music/Track");
76  m_shortcuts.insert(UPnPShortcutFeature::MUSIC_ALBUMS, "Music/Album");
77  m_shortcuts.insert(UPnPShortcutFeature::MUSIC_ARTISTS, "Music/Artist");
78  m_shortcuts.insert(UPnPShortcutFeature::MUSIC_GENRES, "Music/Genre");
79 }
80 
82 //
84 
86 {
87  if (m_pRoot)
88  return;
89 
91  m_sName,
92  "0");
93 
94  QString containerId = m_sExtensionId + "/%1";
95 
96  // HACK: I'm not entirely happy with this solution, but it's at least
97  // tidier than passing through half a dozen extra args to Load[Foo]
98  // or having yet more methods just to load the counts
99  auto *pRequest = new UPnpCDSRequest();
100  pRequest->m_nRequestedCount = 0; // We don't want to load any results, we just want the TotalCount
101  auto *pResult = new UPnpCDSExtensionResults();
102  IDTokenMap tokens;
103  // END HACK
104 
105  // -----------------------------------------------------------------------
106  // All Tracks
107  // -----------------------------------------------------------------------
108  CDSObject* pContainer = CDSObject::CreateContainer ( containerId.arg("Track"),
109  QObject::tr("All Tracks"),
110  m_sExtensionId, // Parent Id
111  nullptr );
112  // HACK
113  LoadTracks(pRequest, pResult, tokens);
114  pContainer->SetChildCount(pResult->m_nTotalMatches);
115  pContainer->SetChildContainerCount(0);
116  // END HACK
117  m_pRoot->AddChild(pContainer);
118 
119  // -----------------------------------------------------------------------
120  // By Artist
121  // -----------------------------------------------------------------------
122  pContainer = CDSObject::CreateContainer ( containerId.arg("Artist"),
123  QObject::tr("Artist"),
124  m_sExtensionId, // Parent Id
125  nullptr );
126  // HACK
127  LoadArtists(pRequest, pResult, tokens);
128  pContainer->SetChildCount(pResult->m_nTotalMatches);
129  pContainer->SetChildContainerCount(pResult->m_nTotalMatches);
130  // END HACK
131  m_pRoot->AddChild(pContainer);
132 
133  // -----------------------------------------------------------------------
134  // By Album
135  // -----------------------------------------------------------------------
136  pContainer = CDSObject::CreateContainer ( containerId.arg("Album"),
137  QObject::tr("Album"),
138  m_sExtensionId, // Parent Id
139  nullptr );
140  // HACK
141  LoadAlbums(pRequest, pResult, tokens);
142  pContainer->SetChildCount(pResult->m_nTotalMatches);
143  pContainer->SetChildContainerCount(pResult->m_nTotalMatches);
144  // END HACK
145  m_pRoot->AddChild(pContainer);
146 
147  // -----------------------------------------------------------------------
148  // By Genre
149  // -----------------------------------------------------------------------
150  pContainer = CDSObject::CreateContainer ( containerId.arg("Genre"),
151  QObject::tr("Genre"),
152  m_sExtensionId, // Parent Id
153  nullptr );
154  // HACK
155  LoadGenres(pRequest, pResult, tokens);
156  pContainer->SetChildCount(pResult->m_nTotalMatches);
157  pContainer->SetChildContainerCount(pResult->m_nTotalMatches);
158  // END HACK
159  m_pRoot->AddChild(pContainer);
160 
161  // -----------------------------------------------------------------------
162  // By Directory
163  // -----------------------------------------------------------------------
164 // pContainer = CDSObject::CreateStorageSystem ( containerId.arg("Directory"),
165 // QObject::tr("Directory"),
166 // m_sExtensionId, // Parent Id
167 // nullptr );
168 // // HACK
169 // LoadDirectories(pRequest, pResult, tokens);
170 // pContainer->SetChildCount(pResult->m_nTotalMatches);
171 // pContainer->SetChildContainerCount(pResult->m_nTotalMatches);
172 // // END HACK
173 // m_pRoot->AddChild(pContainer);
174 
175  // -----------------------------------------------------------------------
176 
177  // HACK
178  delete pRequest;
179  delete pResult;
180  // END HACK
181 }
182 
184 //
186 
188 {
189  // ----------------------------------------------------------------------
190  // See if we need to modify the request for compatibility
191  // ----------------------------------------------------------------------
192 
193  // Xbox360 compatibility code.
194 
195 // if (pRequest->m_eClient == CDS_ClientXBox &&
196 // pRequest->m_sContainerID == "7")
197 // {
198 // pRequest->m_sObjectId = "Music";
199 //
200 // LOG(VB_UPNP, LOG_INFO,
201 // "UPnpCDSMusic::IsBrowseRequestForUs - Yes, ContainerId == 7");
202 //
203 // return true;
204 // }
205 //
206 // if ((pRequest->m_sObjectId.isEmpty()) &&
207 // (!pRequest->m_sContainerID.isEmpty()))
208 // pRequest->m_sObjectId = pRequest->m_sContainerID;
209 
210  LOG(VB_UPNP, LOG_INFO,
211  "UPnpCDSMusic::IsBrowseRequestForUs - Not sure... Calling base class.");
212 
213  return UPnpCDSExtension::IsBrowseRequestForUs( pRequest );
214 }
215 
217 //
219 
221 {
222  // ----------------------------------------------------------------------
223  // See if we need to modify the request for compatibility
224  // ----------------------------------------------------------------------
225 
226  // XBox 360 compatibility code
227 
228 // if (pRequest->m_eClient == CDS_ClientXBox &&
229 // pRequest->m_sContainerID == "7")
230 // {
231 // pRequest->m_sObjectId = "Music/1";
232 // pRequest->m_sSearchCriteria = "object.container.album.musicAlbum";
233 // pRequest->m_sSearchList.append( pRequest->m_sSearchCriteria );
234 //
235 // LOG(VB_UPNP, LOG_INFO, "UPnpCDSMusic::IsSearchRequestForUs... Yes.");
236 //
237 // return true;
238 // }
239 //
240 // if (pRequest->m_sContainerID == "4")
241 // {
242 // pRequest->m_sObjectId = "Music";
243 // pRequest->m_sSearchCriteria = "object.item.audioItem.musicTrack";
244 // pRequest->m_sSearchList.append( pRequest->m_sSearchCriteria );
245 //
246 // LOG(VB_UPNP, LOG_INFO, "UPnpCDSMusic::IsSearchRequestForUs... Yes.");
247 //
248 // return true;
249 // }
250 //
251 // if ((pRequest->m_sObjectId.isEmpty()) &&
252 // (!pRequest->m_sContainerID.isEmpty()))
253 // pRequest->m_sObjectId = pRequest->m_sContainerID;
254 
255  LOG(VB_UPNP, LOG_INFO,
256  "UPnpCDSMusic::IsSearchRequestForUs.. Don't know, calling base class.");
257 
258  return UPnpCDSExtension::IsSearchRequestForUs( pRequest );
259 }
260 
262 //
264 
266  UPnpCDSExtensionResults* pResults,
267  const IDTokenMap& tokens, const QString& currentToken)
268 {
269  if (currentToken.isEmpty())
270  {
271  LOG(VB_GENERAL, LOG_ERR, QString("UPnpCDSMusic::LoadMetadata: Final "
272  "token missing from id: %1")
273  .arg(pRequest->m_sParentId));
274  return false;
275  }
276 
277  // Root or Root + 1
278  if (tokens[currentToken].isEmpty())
279  {
280  CDSObject *container = nullptr;
281 
282  if (pRequest->m_sObjectId == m_sExtensionId)
283  container = GetRoot();
284  else
285  container = GetRoot()->GetChild(pRequest->m_sObjectId);
286 
287  if (container)
288  {
289  pResults->Add(container);
290  pResults->m_nTotalMatches = 1;
291  return true;
292  }
293 
294  LOG(VB_GENERAL, LOG_ERR, QString("UPnpCDSMusic::LoadMetadata: Requested "
295  "object cannot be found: %1")
296  .arg(pRequest->m_sObjectId));
297  return false;
298  }
299  if (currentToken == "genre")
300  {
301  // Genre is presently a top tier node, since it doesn't appear
302  // below Artist/Album/etc we don't need to pass through
303  // the ids for filtering
304  return LoadGenres(pRequest, pResults, tokens);
305  }
306  if (currentToken == "artist")
307  {
308  return LoadArtists(pRequest, pResults, tokens);
309  }
310  if (currentToken == "album")
311  {
312  return LoadAlbums(pRequest, pResults, tokens);
313  }
314  if (currentToken == "track")
315  {
316  return LoadTracks(pRequest, pResults, tokens);
317  }
318 
319  LOG(VB_GENERAL, LOG_ERR,
320  QString("UPnpCDSMusic::LoadMetadata(): "
321  "Unhandled metadata request for '%1'.").arg(currentToken));
322  return false;
323 }
324 
326 //
328 
330  UPnpCDSExtensionResults* pResults,
331  const IDTokenMap& tokens, const QString& currentToken)
332 {
333  if (currentToken.isEmpty() || currentToken == m_sExtensionId.toLower())
334  {
335  // Root
336  pResults->Add(GetRoot()->GetChildren());
337  pResults->m_nTotalMatches = GetRoot()->GetChildCount();
338  return true;
339  }
340  if (currentToken == "track")
341  {
342  return LoadTracks(pRequest, pResults, tokens);
343  }
344  if (currentToken == "genre")
345  {
346  if (tokens["genre"].toInt() > 0)
347  return LoadArtists(pRequest, pResults, tokens);
348  return LoadGenres(pRequest, pResults, tokens);
349  }
350  if (currentToken == "artist")
351  {
352  if (tokens["artist"].toInt() > 0)
353  return LoadAlbums(pRequest, pResults, tokens);
354  return LoadArtists(pRequest, pResults, tokens);
355  }
356  if (currentToken == "album")
357  {
358  if (tokens["album"].toInt() > 0)
359  return LoadTracks(pRequest, pResults, tokens);
360  return LoadAlbums(pRequest, pResults, tokens);
361  }
362  LOG(VB_GENERAL, LOG_ERR,
363  QString("UPnpCDSMusic::LoadChildren(): "
364  "Unhandled metadata request for '%1'.").arg(currentToken));
365 
366  return false;
367 }
368 
370 //
372 
374 {
375  QUrl artURI = m_uriBase;
376  artURI.setPath("/Content/GetAlbumArt");
377  QUrlQuery artQuery;
378  artQuery.addQueryItem("Id", QString::number(nSongID));
379  artURI.setQuery(artQuery);
380 
381  QList<Property*> propList = pItem->GetProperties("albumArtURI");
382  if (propList.size() >= 4)
383  {
384  // Prefer JPEG over PNG here, although PNG is allowed DLNA requires JPEG
385  // and crucially the filesizes are smaller which speeds up loading times
386  // over the network
387 
388  // We MUST include the thumbnail size, but since some clients may use the
389  // first image they see and the thumbnail is tiny, instead return the
390  // medium first. The large could be very large, which is no good if the
391  // client is pulling images for an entire list at once!
392 
393  // Medium
394  Property *pProp = propList.at(0);
395  if (pProp)
396  {
397  // Must be no more than 1024x768
398  QUrl mediumURI = artURI;
399  QUrlQuery mediumQuery(mediumURI.query());
400  mediumQuery.addQueryItem("Width", "1024");
401  mediumQuery.addQueryItem("Height", "768");
402  mediumURI.setQuery(mediumQuery);
403  pProp->SetValue(mediumURI.toEncoded());
404  pProp->AddAttribute("dlna:profileID", "JPEG_MED");
405  pProp->AddAttribute("xmlns:dlna", "urn:schemas-dlna-org:metadata-1-0");
406  }
407 
408  // Thumbnail
409  pProp = propList.at(1);
410  if (pProp)
411  {
412  // At least one albumArtURI must be a ThumbNail (TN) no larger
413  // than 160x160, and it must also be a jpeg
414  QUrl thumbURI = artURI;
415  QUrlQuery thumbQuery(thumbURI.query());
416  thumbQuery.addQueryItem("Width", "160");
417  thumbQuery.addQueryItem("Height", "160");
418  thumbURI.setQuery(thumbQuery);
419  pProp->SetValue(thumbURI.toEncoded());
420  pProp->AddAttribute("dlna:profileID", "JPEG_TN");
421  pProp->AddAttribute("xmlns:dlna", "urn:schemas-dlna-org:metadata-1-0");
422  }
423 
424  // Small
425  pProp = propList.at(2);
426  if (pProp)
427  {
428  // Must be no more than 640x480
429  QUrl smallURI = artURI;
430  QUrlQuery smallQuery(smallURI.query());
431  smallQuery.addQueryItem("Width", "640");
432  smallQuery.addQueryItem("Height", "480");
433  smallURI.setQuery(smallQuery);
434  pProp->SetValue(smallURI.toEncoded());
435  pProp->AddAttribute("dlna:profileID", "JPEG_SM");
436  pProp->AddAttribute("xmlns:dlna", "urn:schemas-dlna-org:metadata-1-0");
437  }
438 
439  // Large
440  pProp = propList.at(3);
441  if (pProp)
442  {
443  // Must be no more than 4096x4096 - for our purposes, just return
444  // a fullsize image
445  pProp->SetValue(artURI.toEncoded());
446  pProp->AddAttribute("dlna:profileID", "JPEG_LRG");
447  pProp->AddAttribute("xmlns:dlna", "urn:schemas-dlna-org:metadata-1-0");
448  }
449  }
450  else
451  {
452  LOG(VB_GENERAL, LOG_ERR, QString("Unable to designate album artwork "
453  "for '%1' with class '%2' and id '%3'")
454  .arg(pItem->m_sId,
455  pItem->m_sClass,
456  QString::number(nSongID)));
457  }
458 }
459 
461 //
463 
465  UPnpCDSExtensionResults *pResults,
466  const IDTokenMap& tokens)
467 {
468  QString sRequestId = pRequest->m_sObjectId;
469 
470  uint16_t nCount = pRequest->m_nRequestedCount;
471  uint16_t nOffset = pRequest->m_nStartingIndex;
472 
473  // We must use a dedicated connection to get an acccurate value from
474  // FOUND_ROWS()
476 
477  QString sql = "SELECT SQL_CALC_FOUND_ROWS "
478  "a.album_id, a.album_name, t.artist_name, a.year, "
479  "a.compilation, s.song_id, g.genre, "
480  "COUNT(a.album_id), w.albumart_id "
481  "FROM music_albums a "
482  "LEFT JOIN music_artists t ON a.artist_id=t.artist_id "
483  "LEFT JOIN music_songs s ON a.album_id=s.album_id "
484  "LEFT JOIN music_genres g ON s.genre_id=g.genre_id "
485  "LEFT JOIN music_albumart w ON s.song_id=w.song_id "
486  "%1 " // WHERE clauses
487  "GROUP BY a.album_id "
488  "ORDER BY a.album_name "
489  "LIMIT :OFFSET,:COUNT";
490 
491  QStringList clauses;
492  QString whereString = BuildWhereClause(clauses, tokens);
493 
494  query.prepare(sql.arg(whereString));
495 
496  BindValues(query, tokens);
497 
498  query.bindValue(":OFFSET", nOffset);
499  query.bindValue(":COUNT", nCount);
500 
501  if (!query.exec())
502  return false;
503 
504  while (query.next())
505  {
506  int nAlbumID = query.value(0).toInt();
507  QString sAlbumName = query.value(1).toString();
508  QString sArtist = query.value(2).toString();
509  QString sYear = query.value(3).toString();
510  bool bCompilation = query.value(4).toBool();
511  int nSongId = query.value(5).toInt(); // TODO: Allow artwork lookups by album ID
512  QString sGenre = query.value(6).toString();
513  int nTrackCount = query.value(7).toInt();
514  int nAlbumArtID = query.value(8).toInt();
515 
516  CDSObject* pContainer = CDSObject::CreateMusicAlbum( CreateIDString(sRequestId, "Album", nAlbumID),
517  sAlbumName,
518  pRequest->m_sParentId,
519  nullptr );
520  pContainer->SetPropValue("artist", sArtist);
521  pContainer->SetPropValue("date", sYear);
522  pContainer->SetPropValue("genre", sGenre);
523  pContainer->SetChildCount(nTrackCount);
524  pContainer->SetChildContainerCount(0);
525  if (bCompilation)
526  {
527  // Do nothing for now
528  }
529 
530  // Artwork
531 
532  if (nAlbumArtID > 0)
533  PopulateArtworkURIS(pContainer, nSongId);
534 
535  pResults->Add(pContainer);
536  pContainer->DecrRef();
537  }
538 
539  // Just in case FOUND_ROWS() should fail, ensure m_nTotalMatches contains
540  // at least the size of this result set
541  if (query.size() > 0)
542  pResults->m_nTotalMatches = query.size();
543 
544  // Fetch the total number of matches ignoring any LIMITs
545  query.prepare("SELECT FOUND_ROWS()");
546  if (query.exec() && query.next())
547  pResults->m_nTotalMatches = query.value(0).toUInt();
548 
549  return true;
550 }
551 
553 //
555 
557  UPnpCDSExtensionResults *pResults,
558  const IDTokenMap& tokens)
559 {
560  QString sRequestId = pRequest->m_sObjectId;
561 
562  uint16_t nCount = pRequest->m_nRequestedCount;
563  uint16_t nOffset = pRequest->m_nStartingIndex;
564 
565  // We must use a dedicated connection to get an accurate value from
566  // FOUND_ROWS()
568 
569  QString sql = "SELECT SQL_CALC_FOUND_ROWS "
570  "t.artist_id, t.artist_name, CONCAT_WS(',', g.genre), "
571  "COUNT(DISTINCT a.album_id) "
572  "FROM music_artists t "
573  "LEFT JOIN music_albums a ON a.artist_id = t.artist_id "
574  "JOIN music_songs s ON t.artist_id = s.artist_id "
575  "LEFT JOIN music_genres g ON s.genre_id = g.genre_id "
576  "%1 " // WHERE clauses
577  "GROUP BY t.artist_id "
578  "ORDER BY t.artist_name "
579  "LIMIT :OFFSET,:COUNT";
580 
581 
582  QStringList clauses;
583  QString whereString = BuildWhereClause(clauses, tokens);
584 
585  query.prepare(sql.arg(whereString));
586 
587  BindValues(query, tokens);
588 
589  query.bindValue(":OFFSET", nOffset);
590  query.bindValue(":COUNT", nCount);
591 
592  if (!query.exec())
593  return false;
594 
595  while (query.next())
596  {
597  int nArtistId = query.value(0).toInt();
598  QString sArtistName = query.value(1).toString();
599 // QStringList sGenres = query.value(2).toString().split(',');
600  int nAlbumCount = query.value(3).toInt();
601 
602  CDSObject* pContainer = CDSObject::CreateMusicArtist( CreateIDString(sRequestId, "Artist", nArtistId),
603  sArtistName,
604  pRequest->m_sParentId,
605  nullptr );
606 // TODO: Add SetPropValues option for multi-value properties
607 // QStringList::Iterator it;
608 // for (it = sGenres.begin(); it != sGenres.end(); ++it)
609 // {
610 // pContainer->SetPropValues("genre", sGenres);
611 // }
612  pContainer->SetChildCount(nAlbumCount);
613  pContainer->SetChildContainerCount(nAlbumCount);
614 
615  // Artwork
616  //PopulateArtistURIS(pContainer, nArtistId);
617 
618  pResults->Add(pContainer);
619  pContainer->DecrRef();
620  }
621 
622  // Just in case FOUND_ROWS() should fail, ensure m_nTotalMatches contains
623  // at least the size of this result set
624  if (query.size() > 0)
625  pResults->m_nTotalMatches = query.size();
626 
627  // Fetch the total number of matches ignoring any LIMITs
628  query.prepare("SELECT FOUND_ROWS()");
629  if (query.exec() && query.next())
630  pResults->m_nTotalMatches = query.value(0).toUInt();
631 
632  return true;
633 }
634 
636 //
638 
640  UPnpCDSExtensionResults *pResults,
641  const IDTokenMap& tokens )
642 {
643  QString sRequestId = pRequest->m_sObjectId;
644 
645  uint16_t nCount = pRequest->m_nRequestedCount;
646  uint16_t nOffset = pRequest->m_nStartingIndex;
647 
648  // We must use a dedicated connection to get an acccurate value from
649  // FOUND_ROWS()
651 
652  QString sql = "SELECT SQL_CALC_FOUND_ROWS g.genre_id, g.genre, "
653  "COUNT( DISTINCT t.artist_id ) "
654  "FROM music_genres g "
655  "LEFT JOIN music_songs s ON g.genre_id = s.genre_id "
656  "LEFT JOIN music_artists t ON t.artist_id = s.artist_id "
657  "%1 " // WHERE clauses
658  "GROUP BY g.genre_id "
659  "ORDER BY g.genre "
660  "LIMIT :OFFSET,:COUNT";
661 
662  QStringList clauses;
663  QString whereString = BuildWhereClause(clauses, tokens);
664 
665  query.prepare(sql.arg(whereString));
666 
667  BindValues(query, tokens);
668 
669  query.bindValue(":OFFSET", nOffset);
670  query.bindValue(":COUNT", nCount);
671 
672  if (!query.exec())
673  return false;
674 
675  while (query.next())
676  {
677  int nGenreId = query.value(0).toInt();
678  QString sGenreName = query.value(1).toString();
679  int nArtistCount = query.value(2).toInt();
680 
681  CDSObject* pContainer = CDSObject::CreateMusicGenre( CreateIDString(sRequestId, "Genre", nGenreId),
682  sGenreName,
683  pRequest->m_sParentId,
684  nullptr );
685  pContainer->SetPropValue("description", sGenreName);
686 
687  pContainer->SetChildCount(nArtistCount);
688  pContainer->SetChildContainerCount(nArtistCount);
689 
690  pResults->Add(pContainer);
691  pContainer->DecrRef();
692  }
693 
694  // Just in case FOUND_ROWS() should fail, ensure m_nTotalMatches contains
695  // at least the size of this result set
696  if (query.size() > 0)
697  pResults->m_nTotalMatches = query.size();
698 
699  // Fetch the total number of matches ignoring any LIMITs
700  query.prepare("SELECT FOUND_ROWS()");
701  if (query.exec() && query.next())
702  pResults->m_nTotalMatches = query.value(0).toUInt();
703 
704  return true;
705 }
706 
708 //
710 
712  UPnpCDSExtensionResults *pResults,
713  const IDTokenMap& tokens)
714 {
715  QString sRequestId = pRequest->m_sObjectId;
716 
717  uint16_t nCount = pRequest->m_nRequestedCount;
718  uint16_t nOffset = pRequest->m_nStartingIndex;
719 
720  // We must use a dedicated connection to get an acccurate value from
721  // FOUND_ROWS()
723 
724  QString sql = "SELECT SQL_CALC_FOUND_ROWS s.song_id, t.artist_name, "
725  "a.album_name, s.name, "
726  "g.genre, s.year, s.track, "
727  "s.description, s.filename, s.length, s.size, "
728  "s.numplays, s.lastplay, w.albumart_id "
729  "FROM music_songs s "
730  "LEFT JOIN music_artists t ON t.artist_id = s.artist_id "
731  "LEFT JOIN music_albums a ON a.album_id = s.album_id "
732  "LEFT JOIN music_genres g ON g.genre_id = s.genre_id "
733  "LEFT JOIN music_albumart w ON s.song_id = w.song_id "
734  "%1 " // WHERE clauses
735  "GROUP BY s.song_id "
736  "ORDER BY t.artist_name, a.album_name, s.track "
737  "LIMIT :OFFSET,:COUNT";
738 
739  QStringList clauses;
740  QString whereString = BuildWhereClause(clauses, tokens);
741 
742  query.prepare(sql.arg(whereString));
743 
744  BindValues(query, tokens);
745 
746  query.bindValue(":OFFSET", nOffset);
747  query.bindValue(":COUNT", nCount);
748 
749  if (!query.exec())
750  return false;
751 
752  while (query.next())
753  {
754  int nId = query.value( 0).toInt();
755  QString sArtist = query.value( 1).toString();
756  QString sAlbum = query.value( 2).toString();
757  QString sTitle = query.value( 3).toString();
758  QString sGenre = query.value( 4).toString();
759  int nYear = query.value( 5).toInt();
760  int nTrackNum = query.value( 6).toInt();
761  QString sDescription = query.value( 7).toString();
762  QString sFileName = query.value( 8).toString();
763  auto nLengthMS = std::chrono::milliseconds(query.value( 9).toUInt());
764  uint64_t nFileSize = query.value(10).toULongLong();
765 
766  int nPlaybackCount = query.value(11).toInt();
767  QDateTime lastPlayedTime = query.value(12).toDateTime();
768  int nAlbumArtID = query.value(13).toInt();
769 
770  CDSObject* pItem = CDSObject::CreateMusicTrack( CreateIDString(sRequestId, "Track", nId),
771  sTitle,
772  pRequest->m_sParentId,
773  nullptr );
774 
775  // Only add the reference ID for items which are not in the
776  // 'All Tracks' container
777  QString sRefIDBase = QString("%1/Track").arg(m_sExtensionId);
778  if ( pRequest->m_sParentId != sRefIDBase )
779  {
780  QString sRefId = QString( "%1=%2")
781  .arg( sRefIDBase )
782  .arg( nId );
783 
784  pItem->SetPropValue( "refID", sRefId );
785  }
786 
787  pItem->SetPropValue( "genre" , sGenre );
788  pItem->SetPropValue( "description" , sTitle );
789  pItem->SetPropValue( "longDescription" , sDescription);
790 
791  pItem->SetPropValue( "artist" , sArtist );
792  pItem->SetPropValue( "creator" , sArtist );
793  pItem->SetPropValue( "album" , sAlbum );
794  pItem->SetPropValue( "originalTrackNumber" , QString::number(nTrackNum));
795  if (nYear > 0 && nYear < 9999)
796  pItem->SetPropValue( "date", QDate(nYear,1,1).toString(Qt::ISODate));
797 
798  pItem->SetPropValue( "playbackCount" , QString::number(nPlaybackCount));
799  pItem->SetPropValue( "lastPlaybackTime" , UPnPDateTime::DateTimeFormat(lastPlayedTime));
800 
801  // Artwork
802  if (nAlbumArtID > 0)
803  PopulateArtworkURIS(pItem, nId);
804 
805  // ----------------------------------------------------------------------
806  // Add Music Resource Element based on File extension (HTTP)
807  // ----------------------------------------------------------------------
808 
809  QFileInfo fInfo( sFileName );
810 
811  QUrl resURI = m_uriBase;
812  QUrlQuery resQuery;
813  resURI.setPath("/Content/GetMusic");
814  resQuery.addQueryItem("Id", QString::number(nId));
815  resURI.setQuery(resQuery);
816 
817  QString sMimeType = HTTPRequest::GetMimeType( fInfo.suffix() );
818 
819  QString sProtocol = DLNA::ProtocolInfoString(UPNPProtocol::kHTTP,
820  sMimeType);
821 
822  Resource *pResource = pItem->AddResource( sProtocol, resURI.toEncoded() );
823 
824  pResource->AddAttribute( "duration" , UPnPDateTime::resDurationFormat(nLengthMS) );
825  if (nFileSize > 0)
826  pResource->AddAttribute( "size" , QString::number( nFileSize) );
827 
828  pResults->Add(pItem);
829  pItem->DecrRef();
830  }
831 
832  // Just in case FOUND_ROWS() should fail, ensure m_nTotalMatches contains
833  // at least the size of this result set
834  if (query.size() > 0)
835  pResults->m_nTotalMatches = query.size();
836 
837  // Fetch the total number of matches ignoring any LIMITs
838  query.prepare("SELECT FOUND_ROWS()");
839  if (query.exec() && query.next())
840  pResults->m_nTotalMatches = query.value(0).toUInt();
841 
842  return true;
843 }
844 
846 //
848 
849 QString UPnpCDSMusic::BuildWhereClause( QStringList clauses,
850  IDTokenMap tokens)
851 {
852  if (tokens["track"].toInt() > 0)
853  clauses.append("s.song_id=:TRACK_ID");
854  if (tokens["album"].toInt() > 0)
855  clauses.append("s.album_id=:ALBUM_ID");
856  if (tokens["artist"].toInt() > 0)
857  clauses.append("s.artist_id=:ARTIST_ID");
858  if (tokens["genre"].toInt() > 0)
859  clauses.append("s.genre_id=:GENRE_ID");
860  if (tokens["year"].toInt() > 0)
861  clauses.append("s.year=:YEAR");
862  if (tokens["directory"].toInt() > 0)
863  clauses.append("s.directory_id=:DIRECTORY_ID");
864 
865 // if (tokens["album"].toInt() > 0)
866 // clauses.append("a.album_id=:ALBUM_ID");
867 // if (tokens["artist"].toInt() > 0)
868 // clauses.append("a.artist_id=:ARTIST_ID");
869 
870  QString whereString;
871  if (!clauses.isEmpty())
872  {
873  whereString = " WHERE ";
874  whereString.append(clauses.join(" AND "));
875  }
876 
877  return whereString;
878 }
879 
881 //
883 
885  IDTokenMap tokens)
886 {
887  if (tokens["track"].toInt() > 0)
888  query.bindValue(":TRACK_ID", tokens["track"]);
889  if (tokens["album"].toInt() > 0)
890  query.bindValue(":ALBUM_ID", tokens["album"]);
891  if (tokens["artist"].toInt() > 0)
892  query.bindValue(":ARTIST_ID", tokens["artist"]);
893  if (tokens["genre"].toInt() > 0)
894  query.bindValue(":GENRE_ID", tokens["genre"]);
895  if (tokens["year"].toInt() > 0)
896  query.bindValue(":YEAR", tokens["year"]);
897  if (tokens["directory"].toInt() > 0)
898  query.bindValue(":DIRECTORY_ID", tokens["directory"]);
899 }
900 
901 // vim:ts=4:sw=4:ai:et:si:sts=4
CDSObject::CreateMusicGenre
static CDSObject * CreateMusicGenre(const QString &sId, const QString &sTitle, const QString &sParentId, CDSObject *pObject=nullptr)
Definition: upnpcdsobjects.cpp:887
CDSObject::GetProperties
QList< Property * > GetProperties(const QString &sName)
Definition: upnpcdsobjects.cpp:97
MSqlQuery::next
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:813
MSqlQuery
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:127
UPnpCDSRequest::m_nRequestedCount
uint16_t m_nRequestedCount
Definition: upnpcds.h:81
MythDate::toString
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:84
MSqlQuery::size
int size(void) const
Definition: mythdbcon.h:214
UPnpCDSMusic::CreateRoot
void CreateRoot() override
Definition: upnpcdsmusic.cpp:85
UPnpCDSExtension
Definition: upnpcds.h:203
ReferenceCounter::DecrRef
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
Definition: referencecounter.cpp:125
UPNPProtocol::kHTTP
@ kHTTP
Definition: upnphelpers.h:133
UPnpCDSMusic::UPnpCDSMusic
UPnpCDSMusic()
Music Extension for UPnP ContentDirectory Service.
Definition: upnpcdsmusic.cpp:63
UPnpCDSMusic::IsSearchRequestForUs
bool IsSearchRequestForUs(UPnpCDSRequest *pRequest) override
Definition: upnpcdsmusic.cpp:220
CDSObject
Definition: upnpcdsobjects.h:184
UPnpCDSRequest::m_sParentId
QString m_sParentId
Definition: upnpcds.h:86
UPnpCDSMusic::LoadMetadata
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.
Definition: upnpcdsmusic.cpp:265
UPnPDateTime::DateTimeFormat
QString DateTimeFormat(const QDateTime &dateTime)
Date-Time Format.
Definition: upnphelpers.cpp:43
MythCoreContext::GetBackendStatusPort
int GetBackendStatusPort(void)
Returns the locally defined backend status port.
Definition: mythcorecontext.cpp:1092
Resource
Definition: upnpcdsobjects.h:103
UPnpCDSMusic::PopulateArtworkURIS
void PopulateArtworkURIS(CDSObject *pItem, int songID)
Definition: upnpcdsmusic.cpp:373
UPnpCDSExtension::CreateIDString
static QString CreateIDString(const QString &RequestId, const QString &Name, int Value)
Definition: upnpcds.cpp:1049
MSqlQuery::value
QVariant value(int i) const
Definition: mythdbcon.h:204
UPnpCDSExtension::m_sName
QString m_sName
Definition: upnpcds.h:208
UPnpCDSRequest::m_nStartingIndex
uint16_t m_nStartingIndex
Definition: upnpcds.h:80
UPnPShortcutFeature::MUSIC_ALL
@ MUSIC_ALL
Definition: upnpcds.h:161
MSqlQuery::exec
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:619
UPnpCDSExtension::IsSearchRequestForUs
virtual bool IsSearchRequestForUs(UPnpCDSRequest *pRequest)
Definition: upnpcds.cpp:888
Resource::AddAttribute
void AddAttribute(const QString &sName, const QString &sValue)
Definition: upnpcdsobjects.h:121
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
DLNA::ProtocolInfoString
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.
Definition: upnphelpers.cpp:315
CDSObject::SetChildContainerCount
void SetChildContainerCount(uint32_t nCount)
Allows the caller to set childContainerCount without having to load children.
Definition: upnpcdsobjects.cpp:253
Property
Definition: upnpcdsobjects.h:45
UPnpCDSMusic::LoadChildren
bool LoadChildren(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens, const QString &currentToken) override
Fetch the children of the container identified in the request.
Definition: upnpcdsmusic.cpp:329
Property::AddAttribute
void AddAttribute(const QString &sName, const QString &sValue)
Definition: upnpcdsobjects.h:86
UPnpCDSExtension::m_shortcuts
CDSShortCutList m_shortcuts
Definition: upnpcds.h:211
UPnPShortcutFeature::MUSIC
@ MUSIC
Definition: upnpcds.h:152
MSqlQuery::kDedicatedConnection
@ kDedicatedConnection
Definition: mythdbcon.h:228
Property::SetValue
void SetValue(const QString &value)
Definition: upnpcdsobjects.h:71
HTTPRequest::GetMimeType
static QString GetMimeType(const QString &sFileExtension)
Definition: httprequest.cpp:978
UPnpCDSExtensionResults::m_nTotalMatches
uint16_t m_nTotalMatches
Definition: upnpcds.h:114
MSqlQuery::InitCon
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:551
UPnpCDSExtensionResults::Add
void Add(CDSObject *pObject)
Definition: upnpcds.cpp:32
CDSObject::SetPropValue
void SetPropValue(const QString &sName, const QString &sValue, const QString &type="")
Definition: upnpcdsobjects.cpp:115
MythCoreContext::GetBackendServerIP
QString GetBackendServerIP(void)
Returns the IP address of the locally defined backend IP.
Definition: mythcorecontext.cpp:1004
UPnpCDSMusic::LoadTracks
bool LoadTracks(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens)
Definition: upnpcdsmusic.cpp:711
UPnpCDSMusic::IsBrowseRequestForUs
bool IsBrowseRequestForUs(UPnpCDSRequest *pRequest) override
Definition: upnpcdsmusic.cpp:187
UPnpCDSMusic::LoadArtists
static bool LoadArtists(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens)
Definition: upnpcdsmusic.cpp:556
UPnpCDSExtension::m_pRoot
CDSObject * m_pRoot
Definition: upnpcds.h:249
storagegroup.h
CDSObject::SetChildCount
void SetChildCount(uint32_t nCount)
Allows the caller to set childCount without having to load children.
Definition: upnpcdsobjects.cpp:230
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:55
CDSObject::AddChild
CDSObject * AddChild(CDSObject *pChild)
Definition: upnpcdsobjects.cpp:163
UPnPShortcutFeature::MUSIC_ALBUMS
@ MUSIC_ALBUMS
Definition: upnpcds.h:153
CDSObject::m_sId
QString m_sId
Definition: upnpcdsobjects.h:193
UPnpCDSRequest::m_sObjectId
QString m_sObjectId
Definition: upnpcds.h:76
UPnpCDSMusic::BindValues
static void BindValues(MSqlQuery &query, IDTokenMap tokens)
Definition: upnpcdsmusic.cpp:884
CDSObject::CreateMusicTrack
static CDSObject * CreateMusicTrack(const QString &sId, const QString &sTitle, const QString &sParentId, CDSObject *pObject=nullptr)
Definition: upnpcdsobjects.cpp:523
upnphelpers.h
UPnpCDSMusic::LoadGenres
static bool LoadGenres(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens)
Definition: upnpcdsmusic.cpp:639
UPnpCDSExtension::GetRoot
virtual CDSObject * GetRoot()
Definition: upnpcds.cpp:1082
mythcorecontext.h
CDSObject::AddResource
Resource * AddResource(const QString &sProtocol, const QString &sURI)
Definition: upnpcdsobjects.cpp:203
MSqlQuery::bindValue
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:889
MythDate::ISODate
@ ISODate
Default UTC.
Definition: mythdate.h:17
IDTokenMap
QMap< QString, QString > IDTokenMap
Definition: upnpcds.h:200
UPnpCDSMusic::LoadAlbums
bool LoadAlbums(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens)
Definition: upnpcdsmusic.cpp:464
CDSObject::CreateContainer
static CDSObject * CreateContainer(const QString &sId, const QString &sTitle, const QString &sParentId, CDSObject *pObject=nullptr)
Definition: upnpcdsobjects.cpp:470
UPnPShortcutFeature::MUSIC_ARTISTS
@ MUSIC_ARTISTS
Definition: upnpcds.h:154
CDSObject::m_sClass
QString m_sClass
Definition: upnpcdsobjects.h:196
CDSObject::CreateMusicAlbum
static CDSObject * CreateMusicAlbum(const QString &sId, const QString &sTitle, const QString &sParentId, CDSObject *pObject=nullptr)
Definition: upnpcdsobjects.cpp:837
uint16_t
unsigned short uint16_t
Definition: iso6937tables.h:3
UPnpCDSMusic::BuildWhereClause
static QString BuildWhereClause(QStringList clauses, IDTokenMap tokens)
Definition: upnpcdsmusic.cpp:849
upnpcdsmusic.h
UPnpCDSExtensionResults
Definition: upnpcds.h:106
UPnpCDSMusic::m_uriBase
QUrl m_uriBase
Definition: upnpcdsmusic.h:47
UPnPShortcutFeature::MUSIC_GENRES
@ MUSIC_GENRES
Definition: upnpcds.h:155
CDSObject::GetChildCount
uint32_t GetChildCount(void) const
Return the number of children in this container.
Definition: upnpcdsobjects.cpp:217
CDSObject::GetChild
CDSObject * GetChild(const QString &sID)
Definition: upnpcdsobjects.cpp:182
httprequest.h
UPnPDateTime::resDurationFormat
QString resDurationFormat(std::chrono::milliseconds msec)
res@duration Format B.2.1.4 res@duration - UPnP ContentDirectory Service 2008, 2013
Definition: upnphelpers.cpp:81
UPnpCDSRequest
Definition: upnpcds.h:72
CDSObject::CreateMusicArtist
static CDSObject * CreateMusicArtist(const QString &sId, const QString &sTitle, const QString &sParentId, CDSObject *pObject=nullptr)
Definition: upnpcdsobjects.cpp:957
MSqlQuery::prepare
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:838
UPnpCDSExtension::m_sExtensionId
QString m_sExtensionId
Definition: upnpcds.h:207
UPnpCDSExtension::IsBrowseRequestForUs
virtual bool IsBrowseRequestForUs(UPnpCDSRequest *pRequest)
Definition: upnpcds.cpp:801