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 {
119 sProfileName = "MPEG_PS_HD_DTS";
120 }
121 else if (isHD)
122 {
123 // Fallthough, no DLNA profiles
124 }
125 else if (audioCodec == "DTS")
126 {
127 sProfileName = "MPEG_PS_SD_DTS";
128 }
129 else
130 {
131 if (isNorthAmerica)
132 sProfileName = "MPEG_PS_NTSC";
133 else
134 sProfileName = "MPEG_PS_PAL";
135 }
136 }
137 else if (container == "MPEG2-TS")
138 {
139 if (isNorthAmerica)
140 {
141 if (vidCodec == "H264")
142 sProfileName = "AVC_TS_NA_ISO";
143 else if (isHD) // && videoCodec == "MPEG2VIDEO"
144 sProfileName = "MPEG_TS_HD_NA_ISO";
145 else // if (videoCodec == "MPEG2VIDEO")
146 sProfileName = "MPEG_TS_SD_NA_ISO";
147 }
148 else // Europe standard (DVB)
149 {
150 if (vidCodec == "H264" || isHD) // All HD is AVC with DVB
151 sProfileName = "AVC_TS_EU_ISO";
152 else // if (videoCodec == "MPEG2VIDEO")
153 sProfileName = "MPEG_TS_SD_EU_ISO";
154 }
155 }
156 else if (mimeType == "video/x-matroska" || container == "MATROSKA")
157 {
158 // TODO: We need to know the video and audio codecs before we can serve
159 // up the correct profile.
160 //
161 // sAdditionalInfoList << "DLNA.ORG_PN=AVC_MKV_SOMETHING";
162 if (vidCodec == "H264")
163 {
164 // TODO We need to know the H264 profile used, for now we go with High Profile
165 // as this covers all - devices supporting High Profile also support
166 // Medium Profile
167 if (audioCodec == "AAC") // No HEAAC detection atm, it's a sub-profile of AAC
168 {
169 sProfileName = "AVC_MKV_HP_HD_AAC_MULT5";
170 }
171 else if (audioCodec == "AC3")
172 {
173 sProfileName = "AVC_MKV_HP_HD_AC3";
174 }
175 else if (audioCodec == "E-AC3") // Up to 35Mbps
176 {
177 sProfileName = "AVC_MKV_HP_HD_EAC3";
178 }
179 else if (audioCodec == "MP3")
180 {
181 sProfileName = "AVC_MKV_HP_HD_MPEG1_L3";
182 }
183 else if (audioCodec == "DTS") // Up to 55Mbps // No DTSE/DTSL detection atm, sub-profiles of DTS
184 {
185 sProfileName = "AVC_MKV_HP_HD_DTS";
186 }
187 else if (audioCodec == "MLP") // Up to 45Mbps
188 {
189 sProfileName = "AVC_MKV_HP_HD_MLP";
190 }
191 }
192 }
193 else if (mimeType == "audio/mpeg")
194 {
195 sProfileName = "MP3X";
196 }
197 else if (mimeType == "audio/x-ms-wma")
198 {
199 sProfileName = "WMAFULL";
200 }
201 else if (mimeType == "audio/vnd.dolby.dd-raw")
202 {
203 sProfileName = "AC3";
204 }
205 else if (mimeType == "audio/mp4")
206 {
207 sProfileName = "AAC_ISO_320";
208 }
209 else if (mimeType == "image/jpeg")
210 {
211 if (resolution.width() <= 160 && resolution.height() <= 160)
212 sProfileName = "JPEG_TN";
213 else if (resolution.width() <= 640 && resolution.height() <= 480)
214 sProfileName = "JPEG_SM";
215 else if (resolution.width() <= 1024 && resolution.height() <= 768)
216 sProfileName = "JPEG_MED";
217 else if (resolution.width() <= 4096 && resolution.height() <= 4096)
218 sProfileName = "JPEG_LRG";
219 }
220 else if (mimeType == "image/png")
221 {
222 if (resolution.width() <= 160 && resolution.height() <= 160)
223 sProfileName = "PNG_TN";
224 else if (resolution.width() <= 4096 && resolution.height() <= 4096)
225 sProfileName = "PNG_LRG";
226 }
227 else if (mimeType == "image/gif")
228 {
229 if (resolution.width() <= 1600 && resolution.height() <= 1200)
230 sProfileName = "GIF_LRG";
231 }
232 else
233 {
234 // Not a DLNA supported profile?
235 }
236
237 return sProfileName;
238}
239
241 const QString &mimeType, const QSize resolution,
242 double videoFrameRate, const QString &container,
243 const QString &videoCodec, const QString &audioCodec,
244 bool isTranscoded)
245{
246
247 QStringList sAdditionalInfoList;
248 //
249 // 4th_field = pn-param [op-param] [ps-param] [ci-param] [flags-param] [ *(other-param)]
250 //
251
252 //
253 // PN-Param (Profile Name)
254 //
255 QString sProfileName = DLNAProfileName(mimeType, resolution, videoFrameRate,
256 container, videoCodec, audioCodec);
257 if (!sProfileName.isEmpty())
258 sAdditionalInfoList << QString("DLNA.ORG_PN=%1").arg(sProfileName);
259
260 //
261 // OP-Param (Operation Parameters)
262 //
263 sAdditionalInfoList << DLNA::OpParamString(protocol);
264
265 //
266 // PS-Param (Play Speed)
267 //
268
269 // Not presently used by MythTV
270
271 //
272 // CI-Param - Optional, may be omitted if value is zero (0)
273 //
274 if (isTranscoded)
275 sAdditionalInfoList << DLNA::ConversionIndicatorString(isTranscoded);
276
277 //
278 // Flags-Param
279 //
280 if (mimeType.startsWith("audio") || mimeType.startsWith("video"))
281 {
282 sAdditionalInfoList << DLNA::FlagsString(DLNA::ktm_s |
285 }
286 else
287 {
288 sAdditionalInfoList << DLNA::FlagsString(DLNA::ktm_i |
291 }
292
293
294 //
295 // Build the complete string
296 //
297 QString sAdditionalInfo = "*";
298 // If we have DLNA additional info and we have the mandatory PN param
299 // then add it to the string. If the PN is missing then we must ignore the
300 // rest
301 // 7.4.1.3.13.8 - "b) The pn-param (DLNA.ORG_PN) is the only required
302 // parameter for DLNA media format profiles."
303 //
304 // 7.4.1.3.17.1 - 4th_field = pn-param [op-param] [ps-param] [ci-param] [flags-param] [ *(other-param)]
305 // - "b) This syntax prohibits the use of the "*" value for
306 // content that conforms to a DLNA media format profile.
307 // Content that does not conform to a DLNA media format
308 // profile can use the "*" value in the 4th field.
309
310 if (!sAdditionalInfoList.isEmpty() &&
311 sAdditionalInfoList.first().startsWith("DLNA.ORG_PN"))
312 sAdditionalInfo = sAdditionalInfoList.join(";");
313
314 return sAdditionalInfo;
315}
316
317
318// NOTE The order of the DLNA args is mandatory - 7.4.1.3.17 MM protocolInfo values: 4th field
320 const QString &mimeType, const QSize resolution,
321 double videoFrameRate, const QString &container,
322 const QString &videoCodec, const QString &audioCodec,
323 bool isTranscoded)
324{
325 QStringList protocolInfoFields;
326
327 //
328 // 1st Field = protocol
329 //
330
331 if (protocol == UPNPProtocol::kHTTP)
332 protocolInfoFields << "http-get";
333 else if (protocol == UPNPProtocol::kRTP)
334 protocolInfoFields << "rtsp-rtp-udp";
335
336 //
337 // 2nd Field =
338 //
339
340 // Not applicable to us, return wildcard
341 protocolInfoFields << "*";
342
343 //
344 // 3rd Field = mime type
345 //
346 protocolInfoFields << mimeType;
347
348 //
349 // 4th Field = Additional Implementation Information (Used by DLNA)
350 //
351 protocolInfoFields << DLNAFourthField(protocol, mimeType, resolution,
352 videoFrameRate, container,
353 videoCodec, audioCodec,
354 isTranscoded);
355
356 if (protocolInfoFields.size() != 4)
357 LOG(VB_GENERAL, LOG_CRIT, "DLNA::ProtocolInfoString() : Invalid number of fields in string");
358
359 QString str = protocolInfoFields.join(":");
360
361 if (str.length() > 256)
362 {
363 LOG(VB_GENERAL, LOG_WARNING, "DLNA::ProtocolInfoString() : ProtocolInfo string exceeds "
364 "256 byte limit for compatibility with older UPnP devices. "
365 "Consider omitting optional DLNA information such as ci-param");
366 }
367 return str;
368}
369
370
371QString FlagsString(uint32_t flags)
372{
373 QString str;
374
375 if (flags == 0)
376 return str;
377
378 // DLNA::kv1_5_flag must be set
379 if ((flags & DLNA::ktm_s) && (flags & DLNA::ktm_i))
380 {
381 LOG(VB_GENERAL, LOG_ERR, "Programmer Error: 'Streaming' and 'Interactive' mode flags are mutally exclusive");
382 flags &= ~(DLNA::ktm_s | DLNA::ktm_i);
383 }
384
385 // DLNA::kv1_5_flag must be set otherwise other flags are ignored
386 if (flags & ~DLNA::kv1_5_flag)
387 flags |= DLNA::kv1_5_flag;
388
389 return QString("DLNA.ORG_FLAGS=%1") // 8 HEX Digits, following by 24 zeros
390 .arg(flags,8,10,QChar('0')) + "000000000000000000000000";
391}
392
394{
395 QString str = "DLNA.ORG_OP=%1%2";
396
397 if (protocol == UPNPProtocol::kHTTP)
398 {
399 // Section 7.4.1.3.20
400 str = str.arg("0", // A-val - DLNA Time Seek header support (not currently supported)
401 "1"); // B-val - HTTP Range support
402 }
403 else if (protocol == UPNPProtocol::kRTP)
404 {
405 // Section 7.4.1.3.21
406 str = str.arg("0", // A-val - Range Header Support
407 "0"); // B-val - Must always zero
408 }
409
410 return str;
411}
412
413QString ConversionIndicatorString(bool wasConverted)
414{
415 QString str = "DLNA.ORG_CI=%1";
416
417 return str.arg(wasConverted ? "1" : "0");
418}
419
420};
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:18
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