1 /**
2 * Copyright (C) 2005-2006 by Koos Vriezen <koos.vriezen@gmail.com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License version 2 as published by the Free Software Foundation.
7 *
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Library General Public License for more details.
12 *
13 * You should have received a copy of the GNU Library General Public License
14 * along with this library; see the file COPYING.LIB. If not, write to
15 * the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
16 * Boston, MA 02110-1301, USA.
17 **/
18
19 #include "config-kmplayer.h"
20 #include <kdebug.h>
21 #include "kmplayer_atom.h"
22 #include "kmplayer_smil.h"
23
24 #include <QTextStream>
25
26 using namespace KMPlayer;
27
childFromTag(const QString & tag)28 Node *ATOM::Feed::childFromTag (const QString & tag) {
29 QByteArray ba = tag.toLatin1 ();
30 const char *name = ba.constData ();
31 if (!strcmp (name, "entry"))
32 return new ATOM::Entry (m_doc);
33 else if (!strcmp (name, "link"))
34 return new ATOM::Link (m_doc);
35 else if (!strcmp (name, "title"))
36 return new DarkNode (m_doc, tag.toUtf8 (), id_node_title);
37 return NULL;
38 }
39
closed()40 void ATOM::Feed::closed () {
41 for (Node *c = firstChild (); c; c = c->nextSibling ())
42 if (c->id == id_node_title) {
43 title = c->innerText ().simplified ();
44 break;
45 }
46 Element::closed ();
47 }
48
role(RoleType msg,void * content)49 void *ATOM::Feed::role (RoleType msg, void *content)
50 {
51 if (RolePlaylist == msg)
52 return !title.isEmpty () ? (PlaylistRole *) this : NULL;
53 return Element::role (msg, content);
54 }
55
childFromTag(const QString & tag)56 Node *ATOM::Entry::childFromTag (const QString &tag) {
57 QByteArray ba = tag.toLatin1 ();
58 const char *cstr = ba.constData ();
59 if (!strcmp (cstr, "link"))
60 return new ATOM::Link (m_doc);
61 else if (!strcmp (cstr, "content"))
62 return new ATOM::Content (m_doc);
63 else if (!strcmp (cstr, "title"))
64 return new DarkNode (m_doc, tag.toUtf8 (), id_node_title);
65 else if (!strcmp (cstr, "summary"))
66 return new DarkNode (m_doc, tag.toUtf8 (), id_node_summary);
67 else if (!strcmp (cstr, "media:group"))
68 return new MediaGroup (m_doc);
69 else if (!strcmp (cstr, "gd:rating"))
70 return new DarkNode (m_doc, tag.toUtf8 (), id_node_gd_rating);
71 else if (!strcmp (cstr, "category") ||
72 !strcmp (cstr, "author:") ||
73 !strcmp (cstr, "id") ||
74 !strcmp (cstr, "updated") ||
75 !strncmp (cstr, "yt:", 3) ||
76 !strncmp (cstr, "gd:", 3))
77 return new DarkNode (m_doc, tag.toUtf8 (), id_node_ignored);
78 return NULL;
79 }
80
closed()81 void ATOM::Entry::closed () {
82 MediaGroup *group = NULL;
83 Node *rating = NULL;
84 for (Node *c = firstChild (); c; c = c->nextSibling ())
85 if (c->id == id_node_title) {
86 title = c->innerText ().simplified ();
87 } else if (c->id == id_node_gd_rating) {
88 rating = c;
89 } else if (c->id == id_node_media_group) {
90 group = static_cast <MediaGroup *> (c);
91 }
92 if (group)
93 group->addSummary (this, rating, QString(), QString(), QString(), 0, 0);
94 Element::closed ();
95 }
96
role(RoleType msg,void * content)97 void *ATOM::Entry::role (RoleType msg, void *content)
98 {
99 if (RolePlaylist == msg)
100 return !title.isEmpty () ? (PlaylistRole *) this : NULL;
101 return Element::role (msg, content);
102 }
103
playType()104 Node::PlayType ATOM::Link::playType () {
105 return src.isEmpty () ? play_type_none : play_type_unknown;
106 }
107
closed()108 void ATOM::Link::closed () {
109 QString href;
110 QString rel;
111 for (Attribute *a = attributes ().first (); a; a = a->nextSibling ()) {
112 if (a->name () == Ids::attr_href)
113 href = a->value ();
114 else if (a->name () == Ids::attr_title)
115 title = a->value ();
116 else if (a->name () == "rel")
117 rel = a->value ();
118 }
119 if (!href.isEmpty () && rel == QString::fromLatin1 ("enclosure"))
120 src = href;
121 else if (title.isEmpty ())
122 title = href;
123 Mrl::closed ();
124 }
125
closed()126 void ATOM::Content::closed () {
127 for (Attribute *a = attributes ().first (); a; a = a->nextSibling ()) {
128 if (a->name () == Ids::attr_src)
129 src = a->value ();
130 else if (a->name () == Ids::attr_type) {
131 QString v = a->value ().toLower ();
132 if (v == QString::fromLatin1 ("text"))
133 mimetype = QString::fromLatin1 ("text/plain");
134 else if (v == QString::fromLatin1 ("html"))
135 mimetype = QString::fromLatin1 ("text/html");
136 else if (v == QString::fromLatin1 ("xhtml"))
137 mimetype = QString::fromLatin1 ("application/xhtml+xml");
138 else
139 mimetype = v;
140 }
141 }
142 Mrl::closed ();
143 }
144
playType()145 Node::PlayType ATOM::Content::playType () {
146 if (!hasChildNodes () && !src.isEmpty ())
147 return play_type_unknown;
148 return play_type_none;
149 }
150
childFromTag(const QString & tag)151 Node *ATOM::MediaGroup::childFromTag (const QString &tag) {
152 QByteArray ba = tag.toLatin1 ();
153 const char *cstr = ba.constData ();
154 if (!strcmp (cstr, "media:content"))
155 return new ATOM::MediaContent (m_doc);
156 else if (!strcmp (cstr, "media:title"))
157 return new DarkNode (m_doc, tag.toUtf8 (), id_node_media_title);
158 else if (!strcmp (cstr, "media:description"))
159 return new DarkNode (m_doc, tag.toUtf8 (), id_node_media_description);
160 else if (!strcmp (cstr, "media:thumbnail"))
161 return new DarkNode (m_doc, tag.toUtf8 (), id_node_media_thumbnail);
162 else if (!strcmp (cstr, "media:player"))
163 return new DarkNode (m_doc, tag.toUtf8 (), id_node_media_player);
164 else if (!strcmp (cstr, "media:category") ||
165 !strcmp (cstr, "media:keywords") ||
166 !strcmp (cstr, "media:credit"))
167 return new DarkNode (m_doc, tag.toUtf8 (), id_node_ignored);
168 else if (!strcmp (cstr, "smil"))
169 return new SMIL::Smil (m_doc);
170 return NULL;
171 }
172
message(MessageType msg,void * content)173 void ATOM::MediaGroup::message (MessageType msg, void *content) {
174 if (MsgChildFinished == msg &&
175 id_node_media_content == ((Posting *) content)->source->id)
176 finish (); // only play one
177 Element::message (msg, content);
178 }
179
makeStar(int x,bool fill)180 static QString makeStar (int x, bool fill) {
181 QString path = "<path style=\"stroke:#A0A0A0;stroke-width:2px;stroke-opacity:1;";
182 if (fill)
183 path += "fill:#ff0000";
184 else
185 path += "fill:#C0C0C0";
186 path += "\" d=\"M 21.428572,23.571429 "
187 "L 10.84984,18.213257 L 0.43866021,23.890134 L 2.2655767,12.173396 "
188 "L -6.3506861,4.0260275 L 5.3571425,2.142857 L 10.443179,-8.5693712 "
189 "L 15.852098,1.9835038 L 27.611704,3.5103513 L 19.246772,11.915557 "
190 "L 21.428572,23.571429 z\""
191 " transform=\"translate(";
192 path += QString::number (x);
193 path += ",11)\"/>";
194 return path;
195 }
196
makeImage(const QString & url,int width,int height)197 static QString makeImage(const QString& url, int width, int height) {
198 QString str = QString ("<img region=\"image\" src=\"") + url + QChar ('"');
199 if (width && height) {
200 str += QString(" width=\"%1\" height=\"%2\"").arg(width).arg(height);
201 }
202 str += QString (" dur=\"20\" transIn=\"fade\" fill=\"transition\" fit=\"meet\"/>");
203 return str;
204 }
205
206 //http://code.google.com/apis/youtube/2.0/developers_guide_protocol.html
addSummary(Node * p,Node * rating_node,const QString & alt_title,const QString & alt_desc,const QString & alt_img,int width,int height)207 void ATOM::MediaGroup::addSummary(Node *p, Node *rating_node,
208 const QString& alt_title, const QString& alt_desc, const QString& alt_img, int width, int height) {
209 QString images;
210 QString desc;
211 QString title;
212 QString player;
213 QString ratings;
214 int img_count = 0;
215 if (rating_node) {
216 Element *e = static_cast <Element *> (rating_node);
217 QString nr = e->getAttribute ("average");
218 if (!nr.isEmpty ()) {
219 int rating = ((int) nr.toDouble ()) % 6;
220 ratings = "<img region=\"rating\">"
221 "<svg width=\"200\" height=\"40\">";
222 for (int i = 0; i < 5; ++i)
223 ratings += makeStar (10 + i * 40, rating > i);
224 ratings += "</svg></img>";
225 }
226 }
227 for (Node *c = firstChild (); c; c = c->nextSibling ()) {
228 switch (c->id) {
229 case id_node_media_title:
230 title = c->innerText ();
231 break;
232 case id_node_media_description:
233 desc = c->innerText ();
234 break;
235 case id_node_media_player:
236 player = static_cast <Element *> (c)->getAttribute (Ids::attr_url);
237 break;
238 case id_node_media_thumbnail:
239 {
240 Element *e = static_cast <Element *> (c);
241 QString url = e->getAttribute (Ids::attr_url);
242 if (!url.isEmpty ()) {
243 images += makeImage(url, e->getAttribute (Ids::attr_width).toInt(),
244 e->getAttribute (Ids::attr_height).toInt());
245 img_count++;
246 }
247 break;
248 }
249 }
250 }
251 if (title.isEmpty())
252 title = alt_title;
253 if (desc.isEmpty())
254 desc = alt_desc;
255 if (!img_count && !alt_img.isEmpty()) {
256 images = makeImage(alt_img, width, height);
257 ++img_count;
258 }
259 if (img_count) {
260 QString buf;
261 QTextStream out (&buf, QIODevice::WriteOnly);
262 out << "<smil><head>";
263 if (!title.isEmpty ())
264 out << "<title>" << title << "</title>";
265 out << "<layout><root-layout width=\"400\" height=\"300\" background-color=\"#F5F5DC\"/>";
266 if (!title.isEmpty ())
267 out << "<region id=\"title\" left=\"20\" top=\"10\" height=\"18\" right=\"10\"/>";
268 out << "<region id=\"image\" left=\"5\" top=\"40\" width=\"130\" bottom=\"30\"/>";
269 if (!ratings.isEmpty ())
270 out << "<region id=\"rating\" left=\"15\" width=\"100\" height=\"20\" bottom=\"5\"/>";
271 out << "<region id=\"text\" left=\"140\" top=\"40\" bottom=\"10\" right=\"10\" fit=\"scroll\"/>"
272 "</layout>"
273 "<transition id=\"fade\" dur=\"0.3\" type=\"fade\"/>"
274 "</head><body>"
275 "<par><seq repeatCount=\"indefinite\">";
276 out << images;
277 out << "</seq>";
278 if (!title.isEmpty ()) {
279 if (!player.isEmpty ())
280 out << "<a href=\"" << XMLStringlet(player) << "\" target=\"top\">";
281 out << "<smilText region=\"title\" textFontWeight=\"bold\" textFontSize=\"11\"";
282 if (!player.isEmpty ())
283 out << " textColor=\"blue\"";
284 out << ">" << XMLStringlet (title) << "</smilText>";
285 if (!player.isEmpty ())
286 out << "</a>";
287 }
288 if (!ratings.isEmpty ())
289 out << ratings;
290 out << "<smilText region=\"text\" textFontFamily=\"serif\" textFontSize=\"11\">";
291 out << XMLStringlet (desc);
292 out << QString ("</smilText></par></body></smil>");
293 QTextStream inxml (&buf, QIODevice::ReadOnly);
294 KMPlayer::readXML (this, inxml, QString (), false);
295 NodePtr n = lastChild();
296 n->normalize ();
297 n->auxiliary_node = true;
298 removeChild (n);
299 p->insertBefore (n, p->firstChild ());
300 }
301 }
302
closed()303 void ATOM::MediaContent::closed () {
304 unsigned fsize = 0;
305 unsigned bitrate = 0;
306 TrieString fs ("fileSize");
307 TrieString rate ("bitrate");
308 for (Attribute *a = attributes ().first (); a; a = a->nextSibling ()) {
309 if (a->name () == Ids::attr_url)
310 src = a->value();
311 else if (a->name () == Ids::attr_type)
312 mimetype = a->value ();
313 else if (a->name () == Ids::attr_height)
314 size.height = a->value ().toInt ();
315 else if (a->name () == Ids::attr_width)
316 size.width = a->value ().toInt ();
317 else if (a->name () == Ids::attr_width)
318 size.width = a->value ().toInt ();
319 else if (a->name () == fs)
320 fsize = a->value ().toInt ();
321 else if (a->name () == rate)
322 bitrate = a->value ().toInt ();
323 }
324 if (!mimetype.isEmpty ()) {
325 title = mimetype;
326 if (fsize > 0) {
327 if (fsize > 1024 * 1024)
328 title += QString (" (%1 Mb)").arg (fsize / (1024 * 1024));
329 else
330 title += QString (" (%1 kb)").arg (fsize / 1024);
331 } else if (bitrate > 0) {
332 if (bitrate > 10 * 1024)
333 title += QString(" (%1 Mbit/s)").arg(bitrate / 1024);
334 else
335 title += QString(" (%1 kbit/s)").arg(bitrate);
336 }
337 }
338 Mrl::closed ();
339 }
340