137 "object.item.videoItem" )
164 pRequest->m_nRequestedCount = 0;
173 QObject::tr(
"All Recordings"),
187 QObject::tr(
"Movies"),
201 QObject::tr(
"Title"),
231 QObject::tr(
"Genre"),
245 QObject::tr(
"Channel"),
259 QObject::tr(
"Recording Group"),
283 const IDTokenMap& tokens,
const QString& currentToken)
285 if (currentToken.isEmpty())
287 LOG(VB_GENERAL, LOG_ERR, QString(
"UPnpCDSTV::LoadMetadata: Final "
288 "token missing from id: %1")
294 if (tokens[currentToken].isEmpty())
305 pResults->
Add(container);
309 LOG(VB_GENERAL, LOG_ERR, QString(
"UPnpCDSTV::LoadMetadata: Requested "
310 "object cannot be found: %1")
313 else if (currentToken ==
"recording")
317 else if (currentToken ==
"title")
319 return LoadTitles(pRequest, pResults, tokens);
321 else if (currentToken ==
"date")
323 return LoadDates(pRequest, pResults, tokens);
325 else if (currentToken ==
"genre")
327 return LoadGenres(pRequest, pResults, tokens);
329 else if (currentToken ==
"recgroup")
333 else if (currentToken ==
"channel")
337 else if (currentToken ==
"movie")
339 return LoadMovies(pRequest, pResults, tokens);
343 LOG(VB_GENERAL, LOG_ERR,
344 QString(
"UPnpCDSTV::LoadMetadata(): "
345 "Unhandled metadata request for '%1'.").arg(currentToken));
357 const IDTokenMap& tokens,
const QString& currentToken)
359 if (currentToken.isEmpty() || currentToken ==
m_sExtensionId.toLower())
366 if (currentToken ==
"title")
368 if (!tokens[
"title"].isEmpty())
370 return LoadTitles(pRequest, pResults, tokens);
372 if (currentToken ==
"date")
374 if (!tokens[
"date"].isEmpty())
376 return LoadDates(pRequest, pResults, tokens);
378 if (currentToken ==
"genre")
380 if (!tokens[
"genre"].isEmpty())
382 return LoadGenres(pRequest, pResults, tokens);
384 if (currentToken ==
"recgroup")
386 if (!tokens[
"recgroup"].isEmpty())
390 if (currentToken ==
"channel")
392 if (tokens[
"channel"].toInt() > 0)
396 if (currentToken ==
"movie")
398 return LoadMovies(pRequest, pResults, tokens);
400 if (currentToken ==
"recording")
404 LOG(VB_GENERAL, LOG_ERR,
405 QString(
"UPnpCDSTV::LoadChildren(): "
406 "Unhandled metadata request for '%1'.").arg(currentToken));
451 LOG(VB_UPNP, LOG_INFO,
452 "UPnpCDSTv::IsBrowseRequestForUs - Not sure... Calling base class.");
535 QString sql =
"SELECT SQL_CALC_FOUND_ROWS "
536 "r.title, r.inetref, r.recordedid, COUNT(*) "
538 "LEFT JOIN recgroups g ON r.recgroup=g.recgroup "
542 "LIMIT :OFFSET,:COUNT";
546 query.
prepare(sql.arg(whereString));
557 QString sTitle = query.
value(0).toString();
558 QString sInetRef = query.
value(1).toString();
559 int nRecordingID = query.
value(2).toInt();
560 int nTitleCount = query.
value(3).toInt();
571 pContainer->
SetPropValue(
"description", QObject::tr(
"%n Episode(s)",
"", nTitleCount));
572 pContainer->
SetPropValue(
"longdescription", QObject::tr(
"%n Episode(s)",
"", nTitleCount));
581 pResults->
Add(pContainer);
587 newTokens.insert(
"recording", QString::number(nRecordingID));
594 if (query.
size() > 0)
598 query.
prepare(
"SELECT FOUND_ROWS()");
622 QString sql =
"SELECT SQL_CALC_FOUND_ROWS "
623 "r.starttime, COUNT(r.recordedid) "
625 "LEFT JOIN recgroups g ON g.recgroup=r.recgroup "
627 "GROUP BY DATE(CONVERT_TZ(r.starttime, 'UTC', 'SYSTEM')) "
628 "ORDER BY r.starttime DESC "
629 "LIMIT :OFFSET,:COUNT";
633 query.
prepare(sql.arg(whereString));
644 QDate dtDate = query.
value(0).toDate();
645 int nRecCount = query.
value(1).toInt();
655 pResults->
Add(pContainer);
661 if (query.
size() > 0)
665 query.
prepare(
"SELECT FOUND_ROWS()");
689 QString sql =
"SELECT SQL_CALC_FOUND_ROWS "
690 "r.category, COUNT(r.recordedid) "
692 "LEFT JOIN recgroups g ON g.recgroup=r.recgroup "
694 "GROUP BY r.category "
695 "ORDER BY r.category "
696 "LIMIT :OFFSET,:COUNT";
700 query.
prepare(sql.arg(whereString));
711 QString sGenre = query.
value(0).toString();
712 int nRecCount = query.
value(1).toInt();
715 QString sDisplayGenre = sGenre.isEmpty() ? QObject::tr(
"No Genre") : sGenre;
716 sGenre = sGenre.isEmpty() ?
"MYTH_NO_GENRE" : sGenre;
726 pResults->
Add(pContainer);
732 if (query.
size() > 0)
736 query.
prepare(
"SELECT FOUND_ROWS()");
760 QString sql =
"SELECT SQL_CALC_FOUND_ROWS "
761 "r.recgroupid, g.displayname, g.recgroup, COUNT(r.recordedid) "
763 "LEFT JOIN recgroups g ON g.recgroup=r.recgroup "
765 "GROUP BY r.recgroup "
766 "ORDER BY g.displayname "
767 "LIMIT :OFFSET,:COUNT";
772 query.
prepare(sql.arg(whereString));
786 QString sDisplayName = query.
value(1).toString();
787 QString sName = query.
value(2).toString();
788 int nRecCount = query.
value(3).toInt();
792 sDisplayName.isEmpty() ? sName : sDisplayName,
798 pResults->
Add(pContainer);
804 if (query.
size() > 0)
808 query.
prepare(
"SELECT FOUND_ROWS()");
832 QString sql =
"SELECT SQL_CALC_FOUND_ROWS "
833 "r.chanid, c.channum, c.name, COUNT(r.recordedid) "
835 "JOIN channel c ON c.chanid=r.chanid "
836 "LEFT JOIN recgroups g ON g.recgroup=r.recgroup "
838 "GROUP BY c.channum "
839 "ORDER BY LPAD(CAST(c.channum AS UNSIGNED), 10, 0), "
840 " LPAD(c.channum, 10, 0)"
841 "LIMIT :OFFSET,:COUNT";
846 query.
prepare(sql.arg(whereString));
858 int nChanID = query.
value(0).toInt();
859 QString sChanNum = query.
value(1).toString();
860 QString sName = query.
value(2).toString();
861 int nRecCount = query.
value(3).toInt();
863 QString sFullName = QString(
"%1 %2").arg(sChanNum, sName);
873 pResults->
Add(pContainer);
879 if (query.
size() > 0)
883 query.
prepare(
"SELECT FOUND_ROWS()");
898 tokens[
"category_type"] =
"movie";
942 if (tokens[
"recording"].toInt() > 0)
952 QString sql =
"SELECT SQL_CALC_FOUND_ROWS "
953 "r.chanid, r.starttime, r.endtime, r.title, "
954 "r.subtitle, r.description, r.category, "
955 "r.hostname, r.recgroup, r.filesize, "
956 "r.basename, r.progstart, r.progend, "
957 "r.storagegroup, r.inetref, "
958 "p.category_type, c.callsign, c.channum, "
959 "p.episode, p.totalepisodes, p.season, "
960 "r.programid, r.seriesid, r.recordid, "
961 "c.default_authority, c.name, "
962 "r.recordedid, r.transcoded, p.videoprop+0, p.audioprop+0, "
963 "f.video_codec, f.audio_codec, f.fps, f.width, f.height, "
966 "LEFT JOIN channel c ON r.chanid=c.chanid "
967 "LEFT JOIN recordedprogram p ON p.chanid=r.chanid "
968 " AND p.starttime=r.progstart "
969 "LEFT JOIN recgroups g ON r.recgroup=g.recgroup "
970 "LEFT JOIN recordedfile f ON r.recordedid=f.recordedid "
973 "LIMIT :OFFSET,:COUNT";
976 QString orderByString =
"ORDER BY r.starttime DESC, r.title";
978 if (!tokens[
"title"].isEmpty())
979 orderByString =
"ORDER BY p.season, p.episode, r.starttime ASC";
984 query.
prepare(sql.arg(whereString, orderByString));
996 int nChanid = query.
value( 0).toInt();
999 QString sTitle = query.
value( 3).toString();
1000 QString sSubtitle = query.
value( 4).toString();
1001 QString sDescription = query.
value( 5).toString();
1002 QString sCategory = query.
value( 6).toString();
1003 QString sHostName = query.
value( 7).toString();
1005 uint64_t nFileSize = query.
value( 9).toULongLong();
1006 QString sBaseName = query.
value(10).toString();
1008 QDateTime dtProgStart =
1010 QDateTime dtProgEnd =
1012 QString sStorageGrp = query.
value(13).toString();
1014 QString sInetRef = query.
value(14).toString();
1015 QString sCatType = query.
value(15).toString();
1016 QString sCallsign = query.
value(16).toString();
1017 QString sChanNum = query.
value(17).toString();
1019 int nEpisode = query.
value(18).toInt();
1020 int nEpisodeTotal = query.
value(19).toInt();
1021 int nSeason = query.
value(20).toInt();
1023 QString sProgramId = query.
value(21).toString();
1024 QString sSeriesId = query.
value(22).toString();
1025 int nRecordId = query.
value(23).toInt();
1027 QString sDefaultAuthority = query.
value(24).toString();
1028 QString sChanName = query.
value(25).toString();
1030 int nRecordedId = query.
value(26).toInt();
1032 bool bTranscoded = query.
value(27).toBool();
1033 int nVideoProps = query.
value(28).toInt();
1036 QString sVideoCodec = query.
value(30).toString();
1037 QString sAudioCodec = query.
value(31).toString();
1038 double dVideoFrameRate = query.
value(32).toDouble();
1039 int nVideoWidth = query.
value(33).toInt();
1040 int nVideoHeight = query.
value(34).toInt();
1041 QString sContainer = query.
value(35).toString();
1058 URIBase.setScheme(
"http");
1068 QString sRefIDBase = QString(
"%1/Recording").arg(
m_sExtensionId);
1071 QString sRefId = QString(
"%1=%2")
1073 .arg( nRecordedId );
1093 if (!sSubtitle.isEmpty())
1096 pItem->
SetPropValue(
"description", sDescription.left(128).append(
" ..."));
1097 pItem->
SetPropValue(
"longDescription", sDescription );
1101 pItem->
SetPropValue(
"channelID" , sChanNum,
"DIGITAL");
1105 int nChanNum = sChanNum.toInt();
1107 pItem->
SetPropValue(
"channelNr" , QString::number(nChanNum) );
1109 if (sCatType !=
"movie")
1117 if ( nEpisode > 0 || nSeason > 0 )
1119 pItem->
SetPropValue(
"episodeNumber" , QString::number(nEpisode));
1120 pItem->
SetPropValue(
"episodeCount" , QString::number(nEpisodeTotal));
1125 auto msecs = std::chrono::milliseconds(dtProgEnd.toMSecsSinceEpoch() - dtProgStart.toMSecsSinceEpoch());
1129 pItem->
SetPropValue(
"srsRecordScheduleID" , QString::number(nRecordId));
1131 if (!sSeriesId.isEmpty())
1135 QString sIdType =
"mythtv.org_XMLTV";
1136 if (sSeriesId.contains(sDefaultAuthority))
1137 sIdType =
"mythtv.org_EIT";
1142 if (!sProgramId.isEmpty())
1146 QString sIdType =
"mythtv.org_XMLTV";
1147 if (sProgramId.contains(sDefaultAuthority))
1148 sIdType =
"mythtv.org_EIT";
1150 pItem->
SetPropValue(
"programID", sProgramId, sIdType );
1169 QString sFilePath = sg.
FindFile(sBaseName);
1172 if ( QFile::exists(sFilePath) )
1192 std::chrono::milliseconds nDurationMS { 0ms };
1204 if (nDurationMS == 0ms)
1206 auto uiStart = std::chrono::milliseconds(dtStartTime.toMSecsSinceEpoch());
1207 auto uiEnd = std::chrono::milliseconds(dtEndTime.toMSecsSinceEpoch());
1208 nDurationMS = (uiEnd - uiStart);
1209 nDurationMS = std::max(0ms, nDurationMS);
1215 QSize resolution = QSize(nVideoWidth, nVideoHeight);
1219 if (sContainer.isEmpty())
1222 if (sMimeType ==
"video/mp2p")
1225 sContainer =
"MPEG2-PS";
1227 sContainer =
"MPEG2-TS";
1229 else if (sMimeType ==
"video/mp2t")
1231 sMimeType =
"video/mp2p";
1232 sContainer =
"MPEG2-TS";
1237 if (sVideoCodec.isEmpty())
1239 if (sMimeType ==
"video/mp2p" || sMimeType ==
"video/mp2t")
1240 sVideoCodec = (nVideoProps & VID_AVC) ?
"H264" :
"MPEG2VIDEO";
1241 else if (sMimeType ==
"video/mp4")
1242 sVideoCodec =
"MPEG4";
1248 if (sMimeType ==
"video/mp2t" || sMimeType ==
"video/mp2p")
1249 sMimeType =
"video/mpeg";
1251 QUrl resURI = URIBase;
1253 resURI.setPath(
"/Content/GetRecording");
1254 resQuery.addQueryItem(
"RecordedId", QString::number(nRecordedId));
1255 resURI.setQuery(resQuery);
1269 if (nDurationMS > 0ms)
1271 if (nVideoHeight > 0 && nVideoWidth > 0)
1272 pRes->
AddAttribute (
"resolution" , QString(
"%1x%2").arg(nVideoWidth).arg(nVideoHeight) );
1273 pRes->
AddAttribute (
"size" , QString::number( nFileSize) );
1280 QUrl previewURI = URIBase;
1281 QUrlQuery previewQuery;
1282 previewURI.setPath(
"/Content/GetPreviewImage");
1283 previewQuery.addQueryItem(
"RecordedId", QString::number(nRecordedId));
1284 previewQuery.addQueryItem(
"Width",
"160");
1285 previewQuery.addQueryItem(
"Format",
"JPG");
1286 previewURI.setQuery(previewQuery);
1290 pItem->
AddResource( sProtocol, previewURI.toEncoded());
1295 if (!sInetRef.isEmpty())
1300 pResults->
Add( pItem );
1306 if (query.
size() > 0)
1310 query.
prepare(
"SELECT FOUND_ROWS()");
1322 int nSeason,
const QUrl& URIBase)
1324 QUrl artURI = URIBase;
1325 artURI.setPath(
"/Content/GetRecordingArtwork");
1326 QUrlQuery artQuery(artURI.query());
1327 artQuery.addQueryItem(
"Inetref", sInetRef);
1328 artQuery.addQueryItem(
"Season", QString::number(nSeason));
1329 artURI.setQuery(artQuery);
1343 QUrl thumbURI = artURI;
1344 QUrlQuery thumbQuery(thumbURI.query());
1345 thumbQuery.addQueryItem(
"Type",
"screenshot");
1346 thumbQuery.addQueryItem(
"Width",
"160");
1347 thumbQuery.addQueryItem(
"Height",
"160");
1348 thumbURI.setQuery(thumbQuery);
1352 QUrl smallURI = artURI;
1353 QUrlQuery smallQuery(smallURI.query());
1354 smallQuery.addQueryItem(
"Type",
"coverart");
1355 smallQuery.addQueryItem(
"Width",
"640");
1356 smallQuery.addQueryItem(
"Height",
"480");
1357 smallURI.setQuery(smallQuery);
1361 QUrl mediumURI = artURI;
1362 QUrlQuery mediumQuery(mediumURI.query());
1363 mediumQuery.addQueryItem(
"Type",
"coverart");
1364 mediumQuery.addQueryItem(
"Width",
"1024");
1365 mediumQuery.addQueryItem(
"Height",
"768");
1366 mediumURI.setQuery(mediumQuery);
1371 QUrl largeURI = artURI;
1372 QUrlQuery largeQuery(largeURI.query());
1373 largeQuery.addQueryItem(
"Type",
"fanart");
1374 largeURI.setQuery(largeQuery);
1376 QList<Property*> propList = pItem->
GetProperties(
"albumArtURI");
1377 if (propList.size() >= 4)
1382 pProp->
SetValue(mediumURI.toEncoded());
1384 pProp->
AddAttribute(
"xmlns:dlna",
"urn:schemas-dlna-org:metadata-1-0");
1387 pProp = propList.at(1);
1390 pProp->
SetValue(thumbURI.toEncoded());
1392 pProp->
AddAttribute(
"xmlns:dlna",
"urn:schemas-dlna-org:metadata-1-0");
1395 pProp = propList.at(2);
1398 pProp->
SetValue(smallURI.toEncoded());
1400 pProp->
AddAttribute(
"xmlns:dlna",
"urn:schemas-dlna-org:metadata-1-0");
1403 pProp = propList.at(3);
1406 pProp->
SetValue(largeURI.toEncoded());
1408 pProp->
AddAttribute(
"xmlns:dlna",
"urn:schemas-dlna-org:metadata-1-0");
1412 if (pItem->
m_sClass.startsWith(
"object.item.videoItem"))
1417 "image/jpeg", QSize(1024, 768));
1418 pItem->
AddResource( sProtocol, mediumURI.toEncoded());
1426 "image/jpeg", QSize(640, 480));
1427 pItem->
AddResource( sProtocol, smallURI.toEncoded());
1430 "image/jpeg", QSize(1920, 1080));
1431 pItem->
AddResource( sProtocol, largeURI.toEncoded());
1450 clauses.append(
"g.password=''");
1455 clauses.append(QString(
"g.recgroup != '%1'").arg(liveTVGroup));
1457 clauses.append(QString(
"g.recgroup != '%1'").arg(deletedGroup));
1459 if (tokens[
"recording"].toInt() > 0)
1460 clauses.append(
"r.recordedid=:RECORDED_ID");
1461 if (!tokens[
"date"].isEmpty())
1462 clauses.append(
"DATE(CONVERT_TZ(r.starttime, 'UTC', 'SYSTEM'))=:DATE");
1463 if (!tokens[
"genre"].isEmpty())
1464 clauses.append(
"r.category=:GENRE");
1465 if (!tokens[
"recgroup"].isEmpty())
1466 clauses.append(
"r.recgroup=:RECGROUP");
1467 if (!tokens[
"title"].isEmpty())
1468 clauses.append(
"r.title=:TITLE");
1469 if (!tokens[
"channel"].isEmpty())
1470 clauses.append(
"r.chanid=:CHANNEL");
1472 if (!tokens[
"category_type"].isEmpty())
1473 clauses.append(
"p.category_type=:CATTYPE");
1475 QString whereString;
1476 if (!clauses.isEmpty())
1478 whereString =
" WHERE ";
1479 whereString.append(clauses.join(
" AND "));
1492 if (tokens[
"recording"].toInt() > 0)
1493 query.
bindValue(
":RECORDED_ID", tokens[
"recording"]);
1494 if (!tokens[
"date"].isEmpty())
1495 query.
bindValue(
":DATE", tokens[
"date"]);
1496 if (!tokens[
"genre"].isEmpty())
1497 query.
bindValue(
":GENRE", tokens[
"genre"] ==
"MYTH_NO_GENRE" ?
"" : tokens[
"genre"]);
1498 if (!tokens[
"recgroup"].isEmpty())
1499 query.
bindValue(
":RECGROUP", tokens[
"recgroup"]);
1500 if (!tokens[
"title"].isEmpty())
1501 query.
bindValue(
":TITLE", tokens[
"title"]);
1502 if (tokens[
"channel"].toInt() > 0)
1503 query.
bindValue(
":CHANNEL", tokens[
"channel"]);
1504 if (!tokens[
"category_type"].isEmpty())
1505 query.
bindValue(
":CATTYPE", tokens[
"category_type"]);