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
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();
69  int sPort = gCoreContext->GetBackendStatusPort();
70  m_uriBase.setScheme("http");
71  m_uriBase.setHost(sServerIp);
72  m_uriBase.setPort(sPort);
73 
74  // ShortCuts
75  m_shortcuts.insert(UPnPShortcutFeature::MUSIC, "Music");
76  m_shortcuts.insert(UPnPShortcutFeature::MUSIC_ALL, "Music/Track");
77  m_shortcuts.insert(UPnPShortcutFeature::MUSIC_ALBUMS, "Music/Album");
78  m_shortcuts.insert(UPnPShortcutFeature::MUSIC_ARTISTS, "Music/Artist");
79  m_shortcuts.insert(UPnPShortcutFeature::MUSIC_GENRES, "Music/Genre");
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 
214  return UPnpCDSExtension::IsBrowseRequestForUs( pRequest );
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 
259  return UPnpCDSExtension::IsSearchRequestForUs( pRequest );
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 
820  QString sProtocol = DLNA::ProtocolInfoString(UPNPProtocol::kHTTP,
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 
850 QString 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
CDSObject::CreateMusicGenre
static CDSObject * CreateMusicGenre(const QString &sId, const QString &sTitle, const QString &sParentId, CDSObject *pObject=nullptr)
Definition: upnpcdsobjects.cpp:889
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:812
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:93
MSqlQuery::size
int size(void) const
Definition: mythdbcon.h:214
UPnpCDSMusic::CreateRoot
void CreateRoot() override
Definition: upnpcdsmusic.cpp:86
UPnpCDSExtension
Definition: upnpcds.h:203
ReferenceCounter::DecrRef
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
Definition: referencecounter.cpp:124
UPnpCDSMusic::UPnpCDSMusic
UPnpCDSMusic()
Music Extension for UPnP ContentDirectory Service.
Definition: upnpcdsmusic.cpp:64
UPnpCDSMusic::IsSearchRequestForUs
bool IsSearchRequestForUs(UPnpCDSRequest *pRequest) override
Definition: upnpcdsmusic.cpp:221
CDSObject
Definition: upnpcdsobjects.h:186
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:266
UPnPShortcutFeature::MUSIC_ALL
@ MUSIC_ALL
Definition: upnpcds.h:161
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:1098
Resource
Definition: upnpcdsobjects.h:105
UPnpCDSMusic::PopulateArtworkURIS
void PopulateArtworkURIS(CDSObject *pItem, int songID)
Definition: upnpcdsmusic.cpp:374
UPnpCDSExtension::CreateIDString
static QString CreateIDString(const QString &RequestId, const QString &Name, int Value)
Definition: upnpcds.cpp:1053
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
MSqlQuery::exec
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:618
UPnpCDSExtension::IsSearchRequestForUs
virtual bool IsSearchRequestForUs(UPnpCDSRequest *pRequest)
Definition: upnpcds.cpp:892
Resource::AddAttribute
void AddAttribute(const QString &sName, const QString &sValue)
Definition: upnpcdsobjects.h:123
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:317
CDSObject::SetChildContainerCount
void SetChildContainerCount(uint32_t nCount)
Allows the caller to set childContainerCount without having to load children.
Definition: upnpcdsobjects.cpp:255
UPnPShortcutFeature::MUSIC_ALBUMS
@ MUSIC_ALBUMS
Definition: upnpcds.h:153
Property
Definition: upnpcdsobjects.h:47
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:330
Property::AddAttribute
void AddAttribute(const QString &sName, const QString &sValue)
Definition: upnpcdsobjects.h:88
UPnpCDSExtension::m_shortcuts
CDSShortCutList m_shortcuts
Definition: upnpcds.h:211
UPnPShortcutFeature::MUSIC
@ MUSIC
Definition: upnpcds.h:152
mythlogging.h
Property::SetValue
void SetValue(const QString &value)
Definition: upnpcdsobjects.h:73
HTTPRequest::GetMimeType
static QString GetMimeType(const QString &sFileExtension)
Definition: httprequest.cpp:986
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:550
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:1010
UPnpCDSMusic::LoadTracks
bool LoadTracks(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens)
Definition: upnpcdsmusic.cpp:712
UPnpCDSMusic::IsBrowseRequestForUs
bool IsBrowseRequestForUs(UPnpCDSRequest *pRequest) override
Definition: upnpcdsmusic.cpp:188
UPnPShortcutFeature::MUSIC_GENRES
@ MUSIC_GENRES
Definition: upnpcds.h:155
UPnpCDSMusic::LoadArtists
static bool LoadArtists(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens)
Definition: upnpcdsmusic.cpp:557
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:232
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:57
CDSObject::AddChild
CDSObject * AddChild(CDSObject *pChild)
Definition: upnpcdsobjects.cpp:165
CDSObject::m_sId
QString m_sId
Definition: upnpcdsobjects.h:195
UPnpCDSRequest::m_sObjectId
QString m_sObjectId
Definition: upnpcds.h:76
UPnpCDSMusic::BindValues
static void BindValues(MSqlQuery &query, IDTokenMap tokens)
Definition: upnpcdsmusic.cpp:885
CDSObject::CreateMusicTrack
static CDSObject * CreateMusicTrack(const QString &sId, const QString &sTitle, const QString &sParentId, CDSObject *pObject=nullptr)
Definition: upnpcdsobjects.cpp:525
upnphelpers.h
UPnpCDSMusic::LoadGenres
static bool LoadGenres(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens)
Definition: upnpcdsmusic.cpp:640
UPNPProtocol::kHTTP
@ kHTTP
Definition: upnphelpers.h:133
UPnpCDSExtension::GetRoot
virtual CDSObject * GetRoot()
Definition: upnpcds.cpp:1086
mythcorecontext.h
CDSObject::AddResource
Resource * AddResource(const QString &sProtocol, const QString &sURI)
Definition: upnpcdsobjects.cpp:205
MSqlQuery::bindValue
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:888
MythDate::ISODate
@ ISODate
Default UTC.
Definition: mythdate.h:17
IDTokenMap
QMap< QString, QString > IDTokenMap
Definition: upnpcds.h:200
MSqlQuery::kDedicatedConnection
@ kDedicatedConnection
Definition: mythdbcon.h:228
UPnpCDSMusic::LoadAlbums
bool LoadAlbums(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens)
Definition: upnpcdsmusic.cpp:465
CDSObject::CreateContainer
static CDSObject * CreateContainer(const QString &sId, const QString &sTitle, const QString &sParentId, CDSObject *pObject=nullptr)
Definition: upnpcdsobjects.cpp:472
CDSObject::m_sClass
QString m_sClass
Definition: upnpcdsobjects.h:198
CDSObject::CreateMusicAlbum
static CDSObject * CreateMusicAlbum(const QString &sId, const QString &sTitle, const QString &sParentId, CDSObject *pObject=nullptr)
Definition: upnpcdsobjects.cpp:839
UPnPShortcutFeature::MUSIC_ARTISTS
@ MUSIC_ARTISTS
Definition: upnpcds.h:154
uint16_t
unsigned short uint16_t
Definition: iso6937tables.h:3
UPnpCDSMusic::BuildWhereClause
static QString BuildWhereClause(QStringList clauses, IDTokenMap tokens)
Definition: upnpcdsmusic.cpp:850
upnpcdsmusic.h
UPnpCDSExtensionResults
Definition: upnpcds.h:106
UPnpCDSMusic::m_uriBase
QUrl m_uriBase
Definition: upnpcdsmusic.h:47
CDSObject::GetChildCount
uint32_t GetChildCount(void) const
Return the number of children in this container.
Definition: upnpcdsobjects.cpp:219
CDSObject::GetChild
CDSObject * GetChild(const QString &sID)
Definition: upnpcdsobjects.cpp:184
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:959
MSqlQuery::prepare
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:837
UPnpCDSExtension::m_sExtensionId
QString m_sExtensionId
Definition: upnpcds.h:207
UPnpCDSExtension::IsBrowseRequestForUs
virtual bool IsBrowseRequestForUs(UPnpCDSRequest *pRequest)
Definition: upnpcds.cpp:805