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