MythTV  master
upnphelpers.cpp
Go to the documentation of this file.
1 
2 #include "upnphelpers.h"
3 
6 
7 namespace UPnPDateTime
8 {
9 
10 QString DurationFormat(std::chrono::milliseconds msec)
11 {
12  // Appendix D. Date&Time Syntax - UPnP ContentDirectory Service 2008, 2013
13  // duration ::= 'P' [n 'D'] time
14  // time ::= HH ':' MM ':' SS
15  //
16  // e.g.
17  //"<upnp:scheduledDuration>P1D02:30:23</upnp:scheduledDuration>"
18  // 1 day 2 Hours 30 Minutes 23 Seconds
19 
20  QString durationStr = "P%1%2";
21  QString dayStr;
22  if ( msec > 24h )
23  {
24  dayStr = QString("D%1").arg((msec % 24h).count());
25  }
26  QString timeStr = UPnPDateTime::TimeFormat(msec);
27 
28  return durationStr.arg(dayStr, timeStr);
29 }
30 
31 QString TimeFormat(const QTime time)
32 {
33  QString timeStr = time.toString("HH:mm:ss");
34  return timeStr;
35 }
36 
37  QString TimeFormat(std::chrono::milliseconds msec)
38 {
39  QTime time = QTime::fromMSecsSinceStartOfDay(msec.count());
40  return time.toString("HH:mm:ss");
41 }
42 
43 QString DateTimeFormat(const QDateTime &dateTime)
44 {
45  QString dateTimeStr = dateTime.toString(Qt::ISODate);
46  return dateTimeStr;
47 }
48 
49 QString NamedDayFormat(const QDateTime &dateTime)
50 {
51  return UPnPDateTime::NamedDayFormat(dateTime.date());
52 }
53 
54 QString NamedDayFormat(const QDate date)
55 {
56  // Note QDate::toString() and QDate::shortDayName() return localized strings
57  // which are of no use to us.
58  //
59  // Valid values per the spec are MON, TUE, WED, THU etc
60  switch (date.dayOfWeek())
61  {
62  case 1:
63  return "MON";
64  case 2:
65  return "TUE";
66  case 3:
67  return "WED";
68  case 4:
69  return "THU";
70  case 5:
71  return "FRI";
72  case 6:
73  return "SAT";
74  case 7:
75  return "SUN";
76  default:
77  return "";
78  }
79 }
80 
81 QString resDurationFormat(std::chrono::milliseconds msec)
82 {
83  // Appendix B.2 Resource Encoding Characteristics Properties
84  // B.2.1.4 res@duration
85 
86  // H[H]:MM:SS[.FS]
87  // H = Hours (1-2 digits),
88  // M = Minutes (2 digits, 0 prefix)
89  // S = Seconds (2 digits, 0 prefix)
90  // FS = Fractional Seconds (milliseconds)
91  QTime time = QTime::fromMSecsSinceStartOfDay(msec.count());
92  return time.toString("H:mm:ss.zzz");
93 }
94 
95 };
96 
97 namespace DLNA
98 {
99 
100 QString DLNAProfileName(const QString &mimeType, const QSize resolution,
101  const double /*videoFrameRate*/, const QString &container,
102  const QString &vidCodec, const QString &audioCodec)
103 {
104  QString sProfileName;
105  bool isHD = (resolution.height() >= 720) || (resolution.width() > 720);
106 
107  //QString sBroadcastStandard = "";
108  // HACK This is just temporary until we start storing more video
109  // information in the database for each file and can determine this
110  // stuff 'properly'
111  QString sCountryCode = gCoreContext->GetLocale()->GetCountryCode();
112  bool isNorthAmerica = (sCountryCode == "us" || sCountryCode == "ca" ||
113  sCountryCode == "mx"); // North America (NTSC/ATSC)
114 
115  if (container == "MPEG2-PS")
116  {
117  if (isHD && audioCodec == "DTS")
118  sProfileName = "MPEG_PS_HD_DTS";
119  else if (isHD)
120  {
121  // Fallthough, no DLNA profiles
122  }
123  else if (audioCodec == "DTS")
124  {
125  sProfileName = "MPEG_PS_SD_DTS";
126  }
127  else
128  {
129  if (isNorthAmerica)
130  sProfileName = "MPEG_PS_NTSC";
131  else
132  sProfileName = "MPEG_PS_PAL";
133  }
134  }
135  else if (container == "MPEG2-TS")
136  {
137  if (isNorthAmerica)
138  {
139  if (vidCodec == "H264")
140  sProfileName = "AVC_TS_NA_ISO";
141  else if (isHD) // && videoCodec == "MPEG2VIDEO"
142  sProfileName = "MPEG_TS_HD_NA_ISO";
143  else // if (videoCodec == "MPEG2VIDEO")
144  sProfileName = "MPEG_TS_SD_NA_ISO";
145  }
146  else // Europe standard (DVB)
147  {
148  if (vidCodec == "H264" || isHD) // All HD is AVC with DVB
149  sProfileName = "AVC_TS_EU_ISO";
150  else // if (videoCodec == "MPEG2VIDEO")
151  sProfileName = "MPEG_TS_SD_EU_ISO";
152  }
153  }
154  else if (mimeType == "video/x-matroska" || container == "MATROSKA")
155  {
156  // TODO: We need to know the video and audio codecs before we can serve
157  // up the correct profile.
158  //
159  // sAdditionalInfoList << "DLNA.ORG_PN=AVC_MKV_SOMETHING";
160  if (vidCodec == "H264")
161  {
162  // TODO We need to know the H264 profile used, for now we go with High Profile
163  // as this covers all - devices supporting High Profile also support
164  // Medium Profile
165  if (audioCodec == "AAC") // No HEAAC detection atm, it's a sub-profile of AAC
166  {
167  sProfileName = "AVC_MKV_HP_HD_AAC_MULT5";
168  }
169  else if (audioCodec == "AC3")
170  {
171  sProfileName = "AVC_MKV_HP_HD_AC3";
172  }
173  else if (audioCodec == "E-AC3") // Up to 35Mbps
174  {
175  sProfileName = "AVC_MKV_HP_HD_EAC3";
176  }
177  else if (audioCodec == "MP3")
178  {
179  sProfileName = "AVC_MKV_HP_HD_MPEG1_L3";
180  }
181  else if (audioCodec == "DTS") // Up to 55Mbps // No DTSE/DTSL detection atm, sub-profiles of DTS
182  {
183  sProfileName = "AVC_MKV_HP_HD_DTS";
184  }
185  else if (audioCodec == "MLP") // Up to 45Mbps
186  {
187  sProfileName = "AVC_MKV_HP_HD_MLP";
188  }
189  }
190  }
191  else if (mimeType == "audio/mpeg")
192  {
193  sProfileName = "MP3X";
194  }
195  else if (mimeType == "audio/x-ms-wma")
196  {
197  sProfileName = "WMAFULL";
198  }
199  else if (mimeType == "audio/vnd.dolby.dd-raw")
200  {
201  sProfileName = "AC3";
202  }
203  else if (mimeType == "audio/mp4")
204  {
205  sProfileName = "AAC_ISO_320";
206  }
207  else if (mimeType == "image/jpeg")
208  {
209  if (resolution.width() <= 160 && resolution.height() <= 160)
210  sProfileName = "JPEG_TN";
211  else if (resolution.width() <= 640 && resolution.height() <= 480)
212  sProfileName = "JPEG_SM";
213  else if (resolution.width() <= 1024 && resolution.height() <= 768)
214  sProfileName = "JPEG_MED";
215  else if (resolution.width() <= 4096 && resolution.height() <= 4096)
216  sProfileName = "JPEG_LRG";
217  }
218  else if (mimeType == "image/png")
219  {
220  if (resolution.width() <= 160 && resolution.height() <= 160)
221  sProfileName = "PNG_TN";
222  else if (resolution.width() <= 4096 && resolution.height() <= 4096)
223  sProfileName = "PNG_LRG";
224  }
225  else if (mimeType == "image/gif")
226  {
227  if (resolution.width() <= 1600 && resolution.height() <= 1200)
228  sProfileName = "GIF_LRG";
229  }
230  else
231  {
232  // Not a DLNA supported profile?
233  }
234 
235  return sProfileName;
236 }
237 
239  const QString &mimeType, const QSize resolution,
240  double videoFrameRate, const QString &container,
241  const QString &videoCodec, const QString &audioCodec,
242  bool isTranscoded)
243 {
244 
245  QStringList sAdditionalInfoList;
246  //
247  // 4th_field = pn-param [op-param] [ps-param] [ci-param] [flags-param] [ *(other-param)]
248  //
249 
250  //
251  // PN-Param (Profile Name)
252  //
253  QString sProfileName = DLNAProfileName(mimeType, resolution, videoFrameRate,
254  container, videoCodec, audioCodec);
255  if (!sProfileName.isEmpty())
256  sAdditionalInfoList << QString("DLNA.ORG_PN=%1").arg(sProfileName);
257 
258  //
259  // OP-Param (Operation Parameters)
260  //
261  sAdditionalInfoList << DLNA::OpParamString(protocol);
262 
263  //
264  // PS-Param (Play Speed)
265  //
266 
267  // Not presently used by MythTV
268 
269  //
270  // CI-Param - Optional, may be omitted if value is zero (0)
271  //
272  if (isTranscoded)
273  sAdditionalInfoList << DLNA::ConversionIndicatorString(isTranscoded);
274 
275  //
276  // Flags-Param
277  //
278  if (mimeType.startsWith("audio") || mimeType.startsWith("video"))
279  {
280  sAdditionalInfoList << DLNA::FlagsString(DLNA::ktm_s |
281  DLNA::ktm_b |
283  }
284  else
285  {
286  sAdditionalInfoList << DLNA::FlagsString(DLNA::ktm_i |
287  DLNA::ktm_b |
289  }
290 
291 
292  //
293  // Build the complete string
294  //
295  QString sAdditionalInfo = "*";
296  // If we have DLNA additional info and we have the mandatory PN param
297  // then add it to the string. If the PN is missing then we must ignore the
298  // rest
299  // 7.4.1.3.13.8 - "b) The pn-param (DLNA.ORG_PN) is the only required
300  // parameter for DLNA media format profiles."
301  //
302  // 7.4.1.3.17.1 - 4th_field = pn-param [op-param] [ps-param] [ci-param] [flags-param] [ *(other-param)]
303  // - "b) This syntax prohibits the use of the "*" value for
304  // content that conforms to a DLNA media format profile.
305  // Content that does not conform to a DLNA media format
306  // profile can use the "*" value in the 4th field.
307 
308  if (!sAdditionalInfoList.isEmpty() &&
309  sAdditionalInfoList.first().startsWith("DLNA.ORG_PN"))
310  sAdditionalInfo = sAdditionalInfoList.join(";");
311 
312  return sAdditionalInfo;
313 }
314 
315 
316 // NOTE The order of the DLNA args is mandatory - 7.4.1.3.17 MM protocolInfo values: 4th field
318  const QString &mimeType, const QSize resolution,
319  double videoFrameRate, const QString &container,
320  const QString &videoCodec, const QString &audioCodec,
321  bool isTranscoded)
322 {
323  QStringList protocolInfoFields;
324 
325  //
326  // 1st Field = protocol
327  //
328 
329  if (protocol == UPNPProtocol::kHTTP)
330  protocolInfoFields << "http-get";
331  else if (protocol == UPNPProtocol::kRTP)
332  protocolInfoFields << "rtsp-rtp-udp";
333 
334  //
335  // 2nd Field =
336  //
337 
338  // Not applicable to us, return wildcard
339  protocolInfoFields << "*";
340 
341  //
342  // 3rd Field = mime type
343  //
344  protocolInfoFields << mimeType;
345 
346  //
347  // 4th Field = Additional Implementation Information (Used by DLNA)
348  //
349  protocolInfoFields << DLNAFourthField(protocol, mimeType, resolution,
350  videoFrameRate, container,
351  videoCodec, audioCodec,
352  isTranscoded);
353 
354  if (protocolInfoFields.size() != 4)
355  LOG(VB_GENERAL, LOG_CRIT, "DLNA::ProtocolInfoString() : Invalid number of fields in string");
356 
357  QString str = protocolInfoFields.join(":");
358 
359  if (str.length() > 256)
360  {
361  LOG(VB_GENERAL, LOG_WARNING, "DLNA::ProtocolInfoString() : ProtocolInfo string exceeds "
362  "256 byte limit for compatibility with older UPnP devices. "
363  "Consider omitting optional DLNA information such as ci-param");
364  }
365  return str;
366 }
367 
368 
369 QString FlagsString(uint32_t flags)
370 {
371  QString str;
372 
373  if (flags == 0)
374  return str;
375 
376  // DLNA::kv1_5_flag must be set
377  if ((flags & DLNA::ktm_s) && (flags & DLNA::ktm_i))
378  {
379  LOG(VB_GENERAL, LOG_ERR, "Programmer Error: 'Streaming' and 'Interactive' mode flags are mutally exclusive");
380  flags &= ~(DLNA::ktm_s | DLNA::ktm_i);
381  }
382 
383  // DLNA::kv1_5_flag must be set otherwise other flags are ignored
384  if (flags & ~DLNA::kv1_5_flag)
385  flags |= DLNA::kv1_5_flag;
386 
387  return QString("DLNA.ORG_FLAGS=%1") // 8 HEX Digits, following by 24 zeros
388  .arg(flags,8,10,QChar('0')) + "000000000000000000000000";
389 }
390 
392 {
393  QString str = "DLNA.ORG_OP=%1%2";
394 
395  if (protocol == UPNPProtocol::kHTTP)
396  {
397  // Section 7.4.1.3.20
398  str = str.arg("0", // A-val - DLNA Time Seek header support (not currently supported)
399  "1"); // B-val - HTTP Range support
400  }
401  else if (protocol == UPNPProtocol::kRTP)
402  {
403  // Section 7.4.1.3.21
404  str = str.arg("0", // A-val - Range Header Support
405  "0"); // B-val - Must always zero
406  }
407 
408  return str;
409 }
410 
411 QString ConversionIndicatorString(bool wasConverted)
412 {
413  QString str = "DLNA.ORG_CI=%1";
414 
415  return str.arg(wasConverted ? "1" : "0");
416 }
417 
418 };
DLNA::ktm_s
@ ktm_s
Definition: upnphelpers.h:254
DLNA::ktm_i
@ ktm_i
Definition: upnphelpers.h:255
UPNPProtocol::TransferProtocol
TransferProtocol
Definition: upnphelpers.h:131
MythCoreContext::GetLocale
MythLocale * GetLocale(void) const
Definition: mythcorecontext.cpp:1766
UPnPDateTime::DateTimeFormat
QString DateTimeFormat(const QDateTime &dateTime)
Date-Time Format.
Definition: upnphelpers.cpp:43
UPNPProtocol::kRTP
@ kRTP
Definition: upnphelpers.h:134
DLNA::OpParamString
QString OpParamString(UPNPProtocol::TransferProtocol protocol)
Create a properly formatted Operations Parameter (op-param) string for the given transport protocol b...
Definition: upnphelpers.cpp:391
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
mythlogging.h
MythLocale::GetCountryCode
QString GetCountryCode() const
Definition: mythlocale.cpp:59
DLNA::kv1_5_flag
@ kv1_5_flag
Definition: upnphelpers.h:262
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:55
upnphelpers.h
DLNA::DLNAFourthField
QString DLNAFourthField(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:238
UPNPProtocol::kHTTP
@ kHTTP
Definition: upnphelpers.h:133
DLNA::ktm_b
@ ktm_b
Definition: upnphelpers.h:256
UPnPDateTime::DurationFormat
QString DurationFormat(std::chrono::milliseconds msec)
Duration Format.
Definition: upnphelpers.cpp:10
mythcorecontext.h
DLNA::DLNAProfileName
QString DLNAProfileName(const QString &mimeType, const QSize resolution, const double, const QString &container, const QString &vidCodec, const QString &audioCodec)
Try to determine a valid DLNA profile name for the file based on the supplied metadata.
Definition: upnphelpers.cpp:100
DLNA::FlagsString
QString FlagsString(uint32_t flags)
Convert an integer composed of DNLA_Flags to a properly formatted string for use in XML.
Definition: upnphelpers.cpp:369
MythDate::ISODate
@ ISODate
Default UTC.
Definition: mythdate.h:17
UPnPDateTime::TimeFormat
QString TimeFormat(const QTime time)
Time Format.
Definition: upnphelpers.cpp:31
UPnPDateTime
Helpers for formatting dates and times to UPnP, DLNA and Dublin Core specifications.
Definition: upnphelpers.cpp:7
UPnPDateTime::NamedDayFormat
QString NamedDayFormat(const QDateTime &dateTime)
Named-Day Format.
Definition: upnphelpers.cpp:49
DLNA::ConversionIndicatorString
QString ConversionIndicatorString(bool wasConverted)
Create a properly formatted Conversion Indicator (ci-param) String.
Definition: upnphelpers.cpp:411
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
DLNA
Helpers for building DLNA flag, strings and profiles.
Definition: upnphelpers.cpp:97