1 /*****************************************************************
5 | Copyright (c) 2004-2010, Plutinosoft, LLC.
7 | http://www.plutinosoft.com
9 | This program is free software; you can redistribute it and/or
10 | modify it under the terms of the GNU General Public License
11 | as published by the Free Software Foundation; either version 2
12 | of the License, or (at your option) any later version.
14 | OEMs, ISVs, VARs and other distributors that combine and
15 | distribute commercially licensed software with Platinum software
16 | and do not wish to distribute the source code for the commercially
17 | licensed software under version 2, or (at your option) any later
18 | version, of the GNU General Public License (the "GPL") must enter
19 | into a commercial license agreement with Plutinosoft, LLC.
20 | licensing@plutinosoft.com
22 | This program is distributed in the hope that it will be useful,
23 | but WITHOUT ANY WARRANTY; without even the implied warranty of
24 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 | GNU General Public License for more details.
27 | You should have received a copy of the GNU General Public License
28 | along with this program; see the file LICENSE.txt. If not, write to
29 | the Free Software Foundation, Inc.,
30 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
31 | http://www.gnu.org/licenses/gpl-2.0.html
33 ****************************************************************/
35 /*----------------------------------------------------------------------
37 +---------------------------------------------------------------------*/
39 #include "PltUtilities.h"
40 #include "PltService.h"
42 NPT_SET_LOCAL_LOGGER("platinum.media.server.didl")
44 /*----------------------------------------------------------------------
46 +---------------------------------------------------------------------*/
47 const char* didl_header
= "<DIDL-Lite xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\""
48 " xmlns:dc=\"http://purl.org/dc/elements/1.1/\""
49 " xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\""
50 " xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\""
51 " xmlns:sec=\"http://www.sec.co.kr/\""
52 " xmlns:xbmc=\"urn:schemas-xbmc-org:metadata-1-0/\">";
53 const char* didl_footer
= "</DIDL-Lite>";
54 const char* didl_namespace_dc
= "http://purl.org/dc/elements/1.1/";
55 const char* didl_namespace_upnp
= "urn:schemas-upnp-org:metadata-1-0/upnp/";
56 const char* didl_namespace_dlna
= "urn:schemas-dlna-org:metadata-1-0/";
57 const char* didl_namespace_xbmc
= "urn:schemas-xbmc-org:metadata-1-0/";
59 /*----------------------------------------------------------------------
60 | PLT_Didl::ConvertFilterToMask
61 +---------------------------------------------------------------------*/
63 PLT_Didl::ConvertFilterToMask(const NPT_String
& filter
)
66 if (filter
.GetLength() == 0) return PLT_FILTER_MASK_ALL
;
68 // a filter string is a comma delimited set of fields identifying
69 // a given DIDL property (or set of properties).
70 // These fields are or start with: upnp:, @, res@, res, dc:, container@
73 const char* s
= filter
;
76 while (s
[i
] != '\0') {
77 int next_comma
= filter
.Find(',', i
);
78 int len
= ((next_comma
< 0)?(int)filter
.GetLength():next_comma
)-i
;
80 if (NPT_String::CompareN(s
+i
, "*", 1) == 0) {
81 // return now, there's no point in parsing the rest
82 return PLT_FILTER_MASK_ALL
;
85 // title is required, so we return a non empty mask
86 mask
|= PLT_FILTER_MASK_TITLE
;
88 if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_TITLE
, len
, true) == 0) {
89 mask
|= PLT_FILTER_MASK_TITLE
;
90 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_REFID
, len
, true) == 0) {
91 mask
|= PLT_FILTER_MASK_REFID
;
92 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_CREATOR
, len
, true) == 0) {
93 mask
|= PLT_FILTER_MASK_CREATOR
;
94 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_ARTIST
, len
, true) == 0) {
95 mask
|= PLT_FILTER_MASK_ARTIST
;
96 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_ACTOR
, len
, true) == 0) {
97 mask
|= PLT_FILTER_MASK_ACTOR
;
98 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_DIRECTOR
, len
, true) == 0) {
99 mask
|= PLT_FILTER_MASK_DIRECTOR
;
100 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_AUTHOR
, len
, true) == 0) {
101 mask
|= PLT_FILTER_MASK_AUTHOR
;
102 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_DATE
, len
, true) == 0) {
103 mask
|= PLT_FILTER_MASK_DATE
;
104 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_ALBUM
, len
, true) == 0) {
105 mask
|= PLT_FILTER_MASK_ALBUM
;
106 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_GENRE
, len
, true) == 0) {
107 mask
|= PLT_FILTER_MASK_GENRE
;
108 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_ALBUMARTURI
, len
, true) == 0 ||
109 NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_ALBUMARTURI_DLNAPROFILEID
, len
, true) == 0) {
110 mask
|= PLT_FILTER_MASK_ALBUMARTURI
;
111 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_DESCRIPTION
, len
, true) == 0) {
112 mask
|= PLT_FILTER_MASK_DESCRIPTION
;
113 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_LONGDESCRIPTION
, len
, true) == 0) {
114 mask
|= PLT_FILTER_MASK_LONGDESCRIPTION
;
115 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_ORIGINALTRACK
, len
, true) == 0) {
116 mask
|= PLT_FILTER_MASK_ORIGINALTRACK
;
117 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_LASTPOSITION
, len
, true) == 0) {
118 mask
|= PLT_FILTER_MASK_LASTPOSITION
;
119 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_LASTPLAYBACK
, len
, true) == 0) {
120 mask
|= PLT_FILTER_MASK_LASTPLAYBACK
;
121 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_PLAYCOUNT
, len
, true) == 0) {
122 mask
|= PLT_FILTER_MASK_PLAYCOUNT
;
123 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_SEARCHABLE
, len
, true) == 0) {
124 mask
|= PLT_FILTER_MASK_SEARCHABLE
;
125 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_SEARCHCLASS
, len
, true) == 0) {
126 mask
|= PLT_FILTER_MASK_SEARCHCLASS
;
127 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_CONTAINER_SEARCHABLE
, len
, true) == 0) {
128 mask
|= PLT_FILTER_MASK_SEARCHABLE
;
129 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_CHILDCOUNT
, len
, true) == 0) {
130 mask
|= PLT_FILTER_MASK_CHILDCOUNT
;
131 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_CONTAINER_CHILDCOUNT
, len
, true) == 0) {
132 mask
|= PLT_FILTER_MASK_CHILDCOUNT
;
133 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_PROGRAMTITLE
, len
, true) == 0) {
134 mask
|= PLT_FILTER_MASK_PROGRAMTITLE
;
135 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_SERIESTITLE
, len
, true) == 0) {
136 mask
|= PLT_FILTER_MASK_SERIESTITLE
;
137 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_EPISODE
, len
, true) == 0) {
138 mask
|= PLT_FILTER_MASK_EPISODE
;
139 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_RATING
, len
, true) == 0) {
140 mask
|= PLT_FILTER_MASK_RATING
;
141 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_PUBLISHER
, len
, true) == 0) {
142 mask
|= PLT_FILTER_MASK_PUBLISHER
;
143 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_RES
, len
, true) == 0) {
144 mask
|= PLT_FILTER_MASK_RES
;
145 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_RES_DURATION
, len
, true) == 0 ||
146 NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_RES_DURATION_SHORT
, len
, true) == 0) {
147 mask
|= PLT_FILTER_MASK_RES
| PLT_FILTER_MASK_RES_DURATION
;
148 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_RES_SIZE
, len
, true) == 0) {
149 mask
|= PLT_FILTER_MASK_RES
| PLT_FILTER_MASK_RES_SIZE
;
150 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_RES_PROTECTION
, len
, true) == 0) {
151 mask
|= PLT_FILTER_MASK_RES
| PLT_FILTER_MASK_RES_PROTECTION
;
152 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_RES_RESOLUTION
, len
, true) == 0) {
153 mask
|= PLT_FILTER_MASK_RES
| PLT_FILTER_MASK_RES_RESOLUTION
;
154 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_RES_BITRATE
, len
, true) == 0) {
155 mask
|= PLT_FILTER_MASK_RES
| PLT_FILTER_MASK_RES_BITRATE
;
156 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_RES_BITSPERSAMPLE
, len
, true) == 0) {
157 mask
|= PLT_FILTER_MASK_RES
| PLT_FILTER_MASK_RES_BITSPERSAMPLE
;
158 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_RES_NRAUDIOCHANNELS
, len
, true) == 0) {
159 mask
|= PLT_FILTER_MASK_RES
| PLT_FILTER_MASK_RES_NRAUDIOCHANNELS
;
160 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_RES_SAMPLEFREQUENCY
, len
, true) == 0) {
161 mask
|= PLT_FILTER_MASK_RES
| PLT_FILTER_MASK_RES_SAMPLEFREQUENCY
;
162 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_EPISODE_COUNT
, len
, true) == 0) {
163 mask
|= PLT_FILTER_MASK_EPISODE_COUNT
;
164 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_EPISODE_SEASON
, len
, true) == 0) {
165 mask
|= PLT_FILTER_MASK_EPISODE_SEASON
;
166 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_XBMC_LASTPLAYERSTATE
, len
, true) == 0) {
167 mask
|= PLT_FILTER_MASK_XBMC_LASTPLAYERSTATE
;
168 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_XBMC_DATEADDED
, len
, true) == 0) {
169 mask
|= PLT_FILTER_MASK_XBMC_DATEADDED
;
170 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_XBMC_RATING
, len
, true) == 0) {
171 mask
|= PLT_FILTER_MASK_XBMC_RATING
;
172 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_XBMC_VOTES
, len
, true) == 0) {
173 mask
|= PLT_FILTER_MASK_XBMC_VOTES
;
174 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_XBMC_ARTWORK
, len
, true) == 0) {
175 mask
|= PLT_FILTER_MASK_XBMC_ARTWORK
;
176 } else if (NPT_String::CompareN(s
+i
, PLT_FILTER_FIELD_XBMC_UNIQUE_IDENTIFIER
, len
, true) == 0) {
177 mask
|= PLT_FILTER_MASK_XBMC_UNIQUE_IDENTIFIER
;
178 } else if (NPT_String::CompareN(s
+ i
, PLT_FILTER_FIELD_XBMC_COUNTRY
, len
, true) == 0) {
179 mask
|= PLT_FILTER_MASK_XBMC_COUNTRY
;
180 } else if (NPT_String::CompareN(s
+ i
, PLT_FILTER_FIELD_XBMC_USERRATING
, len
, true) == 0) {
181 mask
|= PLT_FILTER_MASK_XBMC_USERRATING
;
184 if (next_comma
< 0) {
194 /*----------------------------------------------------------------------
195 | PLT_Didl::AppendXmlUnEscape
196 +---------------------------------------------------------------------*/
198 PLT_Didl::AppendXmlUnEscape(NPT_String
& out
, const char* in
)
201 while (i
<NPT_StringLength(in
)) {
202 if (NPT_String::CompareN(in
+i
, "<", 4) == 0) {
205 } else if (NPT_String::CompareN(in
+i
, ">", 4) == 0) {
208 } else if (NPT_String::CompareN(in
+i
, "&", 5) == 0) {
211 } else if (NPT_String::CompareN(in
+i
, """, 6) == 0) {
214 } else if (NPT_String::CompareN(in
+i
, "'", 6) == 0) {
224 /*----------------------------------------------------------------------
225 | PLT_Didl::AppendXmlEscape
226 +---------------------------------------------------------------------*/
228 PLT_Didl::AppendXmlEscape(NPT_String
& out
, const char* in
)
232 for (int i
=0; i
<(int)NPT_StringLength(in
); i
++) {
233 if (*(in
+i
) == '<') {
235 } else if (*(in
+i
) == '>') {
237 } else if (*(in
+i
) == '&') {
239 } else if (*(in
+i
) == '"') {
241 } else if (*(in
+i
) == '\'') {
249 /*----------------------------------------------------------------------
250 | PLT_Didl::FormatTimeStamp
251 +---------------------------------------------------------------------*/
253 PLT_Didl::FormatTimeStamp(NPT_UInt32 seconds
)
256 int hours
= seconds
/3600;
260 result
+= NPT_String::FromInteger(hours
) + ":";
263 int minutes
= (seconds
/60)%60;
270 result
+= NPT_String::FromInteger(minutes
) + ":";
273 int secs
= seconds
%60;
280 result
+= NPT_String::FromInteger(secs
);
283 result
+= ".000"; // needed for XBOX360 otherwise it won't play the track
287 /*----------------------------------------------------------------------
288 | PLT_Didl::ParseTimeStamp
289 +---------------------------------------------------------------------*/
291 PLT_Didl::ParseTimeStamp(const NPT_String
& timestamp
, NPT_UInt32
& seconds
)
293 // assume a timestamp in the format HH:MM:SS.FFF
295 NPT_String str
= timestamp
;
298 // reset output params first
301 // remove milliseconds first if any
302 if ((separator
= str
.ReverseFind('.')) != -1) {
303 str
= str
.Left(separator
);
306 // look for next separator
307 if ((separator
= str
.ReverseFind(':')) == -1) return NPT_FAILURE
;
310 NPT_CHECK_WARNING(str
.SubString(separator
+1).ToInteger(value
));
312 str
= str
.Left(separator
);
314 // look for next separator
315 if ((separator
= str
.ReverseFind(':')) == -1) return NPT_FAILURE
;
318 NPT_CHECK_WARNING(str
.SubString(separator
+1).ToInteger(value
));
320 str
= str
.Left(separator
);
323 NPT_CHECK_WARNING(str
.ToInteger(value
));
324 seconds
+= 3600*value
;
329 /*----------------------------------------------------------------------
331 +---------------------------------------------------------------------*/
333 PLT_Didl::ToDidl(PLT_MediaObject
& object
, const NPT_String
& filter
, NPT_String
& didl
)
335 NPT_UInt64 mask
= ConvertFilterToMask(filter
);
337 // Allocate enough space for the didl
340 return object
.ToDidl(mask
, didl
);
343 /*----------------------------------------------------------------------
345 +---------------------------------------------------------------------*/
347 PLT_Didl::FromDidl(const char* xml
, PLT_MediaObjectListReference
& objects
)
350 PLT_MediaObject
* object
= NULL
;
351 NPT_XmlNode
* node
= NULL
;
352 NPT_XmlElementNode
* didl
= NULL
;
353 NPT_XmlParser parser
;
355 NPT_LOG_FINE("Parsing Didl...");
357 NPT_CHECK_LABEL_SEVERE(parser
.Parse(xml
, node
), cleanup
);
358 if (!node
|| !node
->AsElementNode()) {
359 NPT_LOG_SEVERE("Invalid node type");
363 didl
= node
->AsElementNode();
365 if (didl
->GetTag().Compare("DIDL-Lite", true)) {
366 NPT_LOG_SEVERE("Invalid node tag");
371 objects
= new PLT_MediaObjectList();
373 // for each child, find out if it's a container or not
374 // and then invoke the FromDidl on it
375 for (NPT_List
<NPT_XmlNode
*>::Iterator children
= didl
->GetChildren().GetFirstItem(); children
; children
++) {
376 NPT_XmlElementNode
* child
= (*children
)->AsElementNode();
377 if (!child
) continue;
379 if (child
->GetTag().Compare("Container", true) == 0) {
380 object
= new PLT_MediaContainer();
381 } else if (child
->GetTag().Compare("item", true) == 0) {
382 object
= new PLT_MediaItem();
384 NPT_LOG_WARNING("Invalid node tag");
388 if (NPT_FAILED(object
->FromDidl(child
))) {
389 NPT_LOG_WARNING_1("Invalid didl for object: %s",
390 (const char*) PLT_XmlHelper::Serialize(*child
, false));
394 objects
->Add(object
);
395 object
= NULL
; // reset to make sure it doesn't get deleted twice in case of error