MythTV master
upnphelpers.cpp
Go to the documentation of this file.
1
2#include "upnphelpers.h"
3
6
7namespace UPnPDateTime
8{
9
10QString 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
31QString 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
43QString DateTimeFormat(const QDateTime &dateTime)
44{
45 QString dateTimeStr = dateTime.toString(Qt::ISODate);
46 return dateTimeStr;
47}
48
49QString NamedDayFormat(const QDateTime &dateTime)
50{
51 return UPnPDateTime::NamedDayFormat(dateTime.date());
52}
53
54QString 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
81QString 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
97namespace DLNA
98{
99
100QString 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 |
283 }
284 else
285 {
286 sAdditionalInfoList << DLNA::FlagsString(DLNA::ktm_i |
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
369QString 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
411QString ConversionIndicatorString(bool wasConverted)
412{
413 QString str = "DLNA.ORG_CI=%1";
414
415 return str.arg(wasConverted ? "1" : "0");
416}
417
418};
MythLocale * GetLocale(void) const
QString GetCountryCode() const
Definition: mythlocale.cpp:59
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
Helpers for building DLNA flag, strings and profiles.
Definition: upnphelpers.cpp:98
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 OpParamString(UPNPProtocol::TransferProtocol protocol)
Create a properly formatted Operations Parameter (op-param) string for the given transport protocol b...
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.
QString FlagsString(uint32_t flags)
Convert an integer composed of DNLA_Flags to a properly formatted string for use in XML.
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.
QString ConversionIndicatorString(bool wasConverted)
Create a properly formatted Conversion Indicator (ci-param) String.
@ ktm_i
Definition: upnphelpers.h:255
@ kv1_5_flag
Definition: upnphelpers.h:262
@ ktm_b
Definition: upnphelpers.h:256
@ ktm_s
Definition: upnphelpers.h:254
@ ISODate
Default UTC.
Definition: mythdate.h:17
Helpers for formatting dates and times to UPnP, DLNA and Dublin Core specifications.
Definition: upnphelpers.cpp:8
QString resDurationFormat(std::chrono::milliseconds msec)
res@duration Format B.2.1.4 res@duration - UPnP ContentDirectory Service 2008, 2013
Definition: upnphelpers.cpp:81
QString NamedDayFormat(const QDateTime &dateTime)
Named-Day Format.
Definition: upnphelpers.cpp:49
QString TimeFormat(const QTime time)
Time Format.
Definition: upnphelpers.cpp:31
QString DurationFormat(std::chrono::milliseconds msec)
Duration Format.
Definition: upnphelpers.cpp:10
QString DateTimeFormat(const QDateTime &dateTime)
Date-Time Format.
Definition: upnphelpers.cpp:43