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