26#define LOC QString("UPnpCDSVideo: ")
27#define LOC_WARN QString("UPnpCDSVideo, Warning: ")
28#define LOC_ERR QString("UPnpCDSVideo, Error: ")
32 "object.item.videoItem" )
61 pRequest->m_nRequestedCount = 0;
70 QObject::tr(
"All Videos"),
84 QObject::tr(
"Movies"),
98 QObject::tr(
"Series"),
121 QObject::tr(
"Genre"),
195 LOG(VB_UPNP, LOG_INFO,
196 "UPnpCDSVideo::IsBrowseRequestForUs - Not sure... Calling base class.");
261 const IDTokenMap& tokens,
const QString& currentToken)
263 if (currentToken.isEmpty())
265 LOG(VB_GENERAL, LOG_ERR, QString(
"UPnpCDSTV::LoadMetadata: Final "
266 "token missing from id: %1")
272 if (tokens[currentToken].isEmpty())
283 pResults->
Add(container);
287 LOG(VB_GENERAL, LOG_ERR, QString(
"UPnpCDSTV::LoadMetadata: Requested "
288 "object cannot be found: %1")
291 else if (currentToken ==
"series")
293 return LoadSeries(pRequest, pResults, tokens);
295 else if (currentToken ==
"season")
299 else if (currentToken ==
"genre")
301 return LoadGenres(pRequest, pResults, tokens);
303 else if (currentToken ==
"movie")
305 return LoadMovies(pRequest, pResults, tokens);
307 else if (currentToken ==
"video")
309 return LoadVideos(pRequest, pResults, tokens);
313 LOG(VB_GENERAL, LOG_ERR,
314 QString(
"UPnpCDSVideo::LoadMetadata(): "
315 "Unhandled metadata request for '%1'.").arg(currentToken));
327 const IDTokenMap& tokens,
const QString& currentToken)
329 if (currentToken.isEmpty() || currentToken ==
m_sExtensionId.toLower())
336 if (currentToken ==
"series")
338 if (!tokens[
"series"].isEmpty())
340 return LoadSeries(pRequest, pResults, tokens);
342 if (currentToken ==
"season")
344 if (!tokens[
"season"].isEmpty() && tokens[
"season"].toInt() >= 0)
345 return LoadVideos(pRequest, pResults, tokens);
348 if (currentToken ==
"genre")
350 if (!tokens[
"genre"].isEmpty())
351 return LoadVideos(pRequest, pResults, tokens);
352 return LoadGenres(pRequest, pResults, tokens);
354 if (currentToken ==
"movie")
356 return LoadMovies(pRequest, pResults, tokens);
358 if (currentToken ==
"video")
360 return LoadVideos(pRequest, pResults, tokens);
362 LOG(VB_GENERAL, LOG_ERR,
363 QString(
"UPnpCDSVideo::LoadChildren(): "
364 "Unhandled metadata request for '%1'.").arg(currentToken));
386 QString sql =
"SELECT SQL_CALC_FOUND_ROWS "
387 "v.title, COUNT(DISTINCT v.season), v.intid "
388 "FROM videometadata v "
392 "LIMIT :OFFSET,:COUNT ";
395 clauses.append(
"contenttype='TELEVISION'");
398 query.
prepare(sql.arg(whereString));
410 QString sTitle = query.
value(0).toString();
411 int nSeasonCount = query.
value(1).toInt();
412 int nVidID = query.
value(2).toInt();
419 pContainer->
SetPropValue(
"description", QObject::tr(
"%n Seasons",
"", nSeasonCount));
420 pContainer->
SetPropValue(
"longdescription", QObject::tr(
"%n Seasons",
"", nSeasonCount));
428 pResults->
Add(pContainer);
434 if (query.
size() >= 0)
438 query.
prepare(
"SELECT FOUND_ROWS()");
462 QString sql =
"SELECT SQL_CALC_FOUND_ROWS "
463 "v.season, COUNT(DISTINCT v.intid), v.intid "
464 "FROM videometadata v "
468 "LIMIT :OFFSET,:COUNT ";
473 query.
prepare(sql.arg(whereString));
485 int nSeason = query.
value(0).toInt();
486 int nVideoCount = query.
value(1).toInt();
487 int nVidID = query.
value(2).toInt();
489 QString sTitle = QObject::tr(
"Season %1").arg(nSeason);
496 pContainer->
SetPropValue(
"description", QObject::tr(
"%n Episode(s)",
"", nVideoCount));
497 pContainer->
SetPropValue(
"longdescription", QObject::tr(
"%n Episode(s)",
"", nVideoCount));
505 pResults->
Add(pContainer);
511 if (query.
size() >= 0)
515 query.
prepare(
"SELECT FOUND_ROWS()");
530 tokens[
"type"] =
"MOVIE";
532 return LoadVideos(pRequest, pResults, tokens);
552 QString sql =
"SELECT SQL_CALC_FOUND_ROWS "
553 "v.category, g.genre, COUNT(DISTINCT v.intid) "
554 "FROM videometadata v "
555 "LEFT JOIN videogenre g ON g.intid=v.category "
559 "LIMIT :OFFSET,:COUNT ";
562 clauses.append(
"v.category != 0");
565 query.
prepare(sql.arg(whereString));
577 int nGenreID = query.
value(0).toInt();
578 QString sName = query.
value(1).toString();
579 int nVideoCount = query.
value(2).toInt();
590 pResults->
Add(pContainer);
596 if (query.
size() >= 0)
600 query.
prepare(
"SELECT FOUND_ROWS()");
624 QString sql =
"SELECT SQL_CALC_FOUND_ROWS "
625 "v.intid, title, subtitle, filename, director, plot, "
626 "rating, year, userrating, length, "
627 "season, episode, coverfile, insertdate, host, "
628 "g.genre, studio, collectionref, contenttype "
629 "FROM videometadata v "
630 "LEFT JOIN videogenre g ON g.intid=v.category "
632 "ORDER BY title, season, episode "
633 "LIMIT :OFFSET,:COUNT ";
638 query.
prepare(sql.arg(whereString));
651 int nVidID = query.
value( 0).toInt();
652 QString sTitle = query.
value( 1).toString();
653 QString sSubtitle = query.
value( 2).toString();
654 QString sFilePath = query.
value( 3).toString();
655 QString sDirector = query.
value( 4).toString();
656 QString sPlot = query.
value( 5).toString();
658 int nYear = query.
value( 7).toInt();
661 auto nLength = std::chrono::minutes(query.
value( 9).toUInt());
663 int nSeason = query.
value(10).toInt();
664 int nEpisode = query.
value(11).toInt();
665 QString sCoverArt = query.
value(12).toString();
666 QDateTime dtInsertDate =
668 QString sHostName = query.
value(14).toString();
669 QString sGenre = query.
value(15).toString();
672 QString sContentType = query.
value(18).toString();
684 if (sHostName.isEmpty())
698 if (sHostName.isEmpty())
715 QString sName = sTitle;
716 if( !sSubtitle.isEmpty() )
718 sName +=
" - " + sSubtitle;
722 URIBase.setScheme(
"http");
727 if (sContentType ==
"MOVIE")
740 if (!sSubtitle.isEmpty())
743 pItem->
SetPropValue(
"description", sPlot.left(128).append(
" ..."));
747 if (nEpisode > 0 || nSeason > 0)
751 pItem->
SetPropValue(
"episodeNumber" , QString::number(nEpisode));
756 if (nYear > 1830 && nYear < 9999)
781 QString sRefId = QString(
"%1=%2")
792 QString sFullFileName = sFilePath;
796 sFullFileName = sgroup.
FindFile( sFullFileName );
798 QFileInfo fInfo( sFullFileName );
813 QUrl resURI = URIBase;
815 resURI.setPath(
"/Content/GetVideo");
816 resQuery.addQueryItem(
"Id", QString::number(nVidID));
817 resURI.setQuery(resQuery);
822 if (sMimeType ==
"video/mp2t" || sMimeType ==
"video/mp2p")
823 sMimeType =
"video/mpeg";
829 pRes->
AddAttribute(
"size" , QString(
"%1").arg(fInfo.size()) );
835 if (!sCoverArt.isEmpty() && (sCoverArt !=
"No Cover"))
840 pResults->
Add( pItem );
846 if (query.
size() >= 0)
850 query.
prepare(
"SELECT FOUND_ROWS()");
860 QUrl artURI = URIBase;
861 artURI.setPath(
"/Content/GetVideoArtwork");
863 artQuery.addQueryItem(
"Id", QString::number(nVidID));
864 artURI.setQuery(artQuery);
878 QUrl thumbURI = artURI;
879 QUrlQuery thumbQuery(thumbURI.query());
880 if (pItem->
m_sClass ==
"object.item.videoItem")
881 thumbQuery.addQueryItem(
"Type",
"screenshot");
883 thumbQuery.addQueryItem(
"Type",
"coverart");
884 thumbQuery.addQueryItem(
"Width",
"160");
885 thumbQuery.addQueryItem(
"Height",
"160");
886 thumbURI.setQuery(thumbQuery);
890 QUrl smallURI = artURI;
891 QUrlQuery smallQuery(smallURI.query());
892 smallQuery.addQueryItem(
"Type",
"coverart");
893 smallQuery.addQueryItem(
"Width",
"640");
894 smallQuery.addQueryItem(
"Height",
"480");
895 smallURI.setQuery(smallQuery);
899 QUrl mediumURI = artURI;
900 QUrlQuery mediumQuery(mediumURI.query());
901 mediumQuery.addQueryItem(
"Type",
"coverart");
902 mediumQuery.addQueryItem(
"Width",
"1024");
903 mediumQuery.addQueryItem(
"Height",
"768");
904 mediumURI.setQuery(mediumQuery);
909 QUrl largeURI = artURI;
910 QUrlQuery largeQuery(largeURI.query());
911 largeQuery.addQueryItem(
"Type",
"fanart");
912 largeURI.setQuery(largeQuery);
914 QList<Property*> propList = pItem->
GetProperties(
"albumArtURI");
915 if (propList.size() >= 4)
920 pProp->
SetValue(mediumURI.toEncoded());
922 pProp->
AddAttribute(
"xmlns:dlna",
"urn:schemas-dlna-org:metadata-1-0");
925 pProp = propList.at(1);
929 pProp->
SetValue(thumbURI.toEncoded());
931 pProp->
AddAttribute(
"xmlns:dlna",
"urn:schemas-dlna-org:metadata-1-0");
934 pProp = propList.at(2);
937 pProp->
SetValue(smallURI.toEncoded());
939 pProp->
AddAttribute(
"xmlns:dlna",
"urn:schemas-dlna-org:metadata-1-0");
942 pProp = propList.at(3);
945 pProp->
SetValue(largeURI.toEncoded());
947 pProp->
AddAttribute(
"xmlns:dlna",
"urn:schemas-dlna-org:metadata-1-0");
951 if (pItem->
m_sClass.startsWith(
"object.item.videoItem"))
956 "image/jpeg", QSize(1024, 768));
957 pItem->
AddResource( sProtocol, mediumURI.toEncoded());
960 "image/jpeg", QSize(160, 160));
961 pItem->
AddResource( sProtocol, thumbURI.toEncoded());
964 "image/jpeg", QSize(640, 480));
965 pItem->
AddResource( sProtocol, smallURI.toEncoded());
968 "image/jpeg", QSize(1920, 1080));
969 pItem->
AddResource( sProtocol, largeURI.toEncoded());
975 if (tokens[
"video"].toInt() > 0)
976 clauses.append(
"v.intid=:VIDEO_ID");
977 if (!tokens[
"series"].isEmpty())
978 clauses.append(
"v.title=:TITLE");
979 if (!tokens[
"season"].isEmpty() && tokens[
"season"].toInt() >= 0)
980 clauses.append(
"v.season=:SEASON");
981 if (!tokens[
"type"].isEmpty())
982 clauses.append(
"v.contenttype=:TYPE");
983 if (tokens[
"genre"].toInt() > 0)
984 clauses.append(
"v.category=:GENRE_ID");
987 if (!clauses.isEmpty())
989 whereString =
" WHERE ";
990 whereString.append(clauses.join(
" AND "));
998 if (tokens[
"video"].toInt() > 0)
999 query.
bindValue(
":VIDEO_ID", tokens[
"video"]);
1000 if (!tokens[
"series"].isEmpty())
1001 query.
bindValue(
":TITLE", tokens[
"series"]);
1002 if (!tokens[
"season"].isEmpty() && tokens[
"season"].toInt() >= 0)
1003 query.
bindValue(
":SEASON", tokens[
"season"]);
1004 if (!tokens[
"type"].isEmpty())
1005 query.
bindValue(
":TYPE", tokens[
"type"]);
1006 if (tokens[
"genre"].toInt() > 0)
1007 query.
bindValue(
":GENRE_ID", tokens[
"genre"]);
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.
void SetChildCount(uint32_t nCount)
Allows the caller to set childCount without having to load children.
static CDSObject * CreateVideoItem(const QString &sId, const QString &sTitle, const QString &sParentId, CDSObject *pObject=nullptr)
static CDSObject * CreateMovie(const QString &sId, const QString &sTitle, const QString &sParentId, CDSObject *pObject=nullptr)
Resource * AddResource(const QString &sProtocol, const QString &sURI)
CDSObject * AddChild(CDSObject *pChild)
static CDSObject * CreateAlbum(const QString &sId, const QString &sTitle, const QString &sParentId, CDSObject *pObject=nullptr)
static CDSObject * CreateMovieGenre(const QString &sId, const QString &sTitle, const QString &sParentId, CDSObject *pObject=nullptr)
CDSObject * GetChild(const QString &sID)
void SetPropValue(const QString &sName, const QString &sValue, const QString &type="")
QList< Property * > GetProperties(const QString &sName)
void SetChildContainerCount(uint32_t nCount)
Allows the caller to set childContainerCount without having to load children.
static QString GetMimeType(const QString &sFileExtension)
QSqlQuery wrapper that fetches a DB connection from the connection pool.
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
QVariant value(int i) const
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
int GetBackendStatusPort(void)
Returns the locally defined backend status port.
QString GetBackendServerIP(void)
Returns the IP address of the locally defined backend IP.
void AddAttribute(const QString &sName, const QString &sValue)
void SetValue(const QString &value)
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
void AddAttribute(const QString &sName, const QString &sValue)
QString FindFile(const QString &filename)
void Add(CDSObject *pObject)
virtual CDSObject * GetRoot()
static QString CreateIDString(const QString &RequestId, const QString &Name, int Value)
virtual bool IsBrowseRequestForUs(UPnpCDSRequest *pRequest)
virtual bool IsSearchRequestForUs(UPnpCDSRequest *pRequest)
CDSShortCutList m_shortcuts
uint16_t m_nRequestedCount
uint16_t m_nStartingIndex
void CreateRoot() override
QMap< QString, int > m_mapBackendPort
bool LoadSeries(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens)
bool LoadChildren(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens, const QString ¤tToken) override
Fetch the children of the container identified in the request.
static bool LoadGenres(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens)
bool LoadMovies(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, IDTokenMap tokens)
static QString BuildWhereClause(QStringList clauses, IDTokenMap tokens)
bool IsSearchRequestForUs(UPnpCDSRequest *pRequest) override
bool IsBrowseRequestForUs(UPnpCDSRequest *pRequest) override
static void BindValues(MSqlQuery &query, IDTokenMap tokens)
bool LoadSeasons(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens)
bool LoadVideos(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens)
QStringMap m_mapBackendIp
static void PopulateArtworkURIS(CDSObject *pItem, int nVidID, const QUrl &URIBase)
bool LoadMetadata(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens, const QString ¤tToken) override
Fetch just the metadata for the item identified in the request.
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
QString ProtocolInfoString(UPNPProtocol::TransferProtocol protocol, const QString &mimeType, const QSize resolution, double videoFrameRate, const QString &container, const QString &videoCodec, const QString &audioCodec, bool isTranscoded)
Create a properly formatted string for the 4th field of res@protocolInfo.
QDateTime as_utc(const QDateTime &old_dt)
Returns copy of QDateTime with TimeSpec set to UTC.
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
QString resDurationFormat(std::chrono::milliseconds msec)
res@duration Format B.2.1.4 res@duration - UPnP ContentDirectory Service 2008, 2013
QString DateTimeFormat(const QDateTime &dateTime)
Date-Time Format.
QMap< QString, QString > IDTokenMap