File indexing completed on 2024-04-14 04:48:55

0001 /*
0002     SPDX-FileCopyrightText: 2005-2006 Koos Vriezen <koos.vriezen@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-only
0005 */
0006 
0007 #include "config-kmplayer.h"
0008 
0009 #include "kmplayercommon_log.h"
0010 #include "kmplayer_atom.h"
0011 #include "kmplayer_smil.h"
0012 
0013 #include <QTextStream>
0014 
0015 using namespace KMPlayer;
0016 
0017 Node *ATOM::Feed::childFromTag (const QString & tag) {
0018     QByteArray ba = tag.toLatin1 ();
0019     const char *name = ba.constData ();
0020     if (!strcmp (name, "entry"))
0021         return new ATOM::Entry (m_doc);
0022     else if (!strcmp (name, "link"))
0023         return new ATOM::Link (m_doc);
0024     else if (!strcmp (name, "title"))
0025         return new DarkNode (m_doc, tag.toUtf8 (), id_node_title);
0026     return nullptr;
0027 }
0028 
0029 void ATOM::Feed::closed () {
0030     for (Node *c = firstChild (); c; c = c->nextSibling ())
0031         if (c->id == id_node_title) {
0032             title = c->innerText ().simplified ();
0033             break;
0034         }
0035     Element::closed ();
0036 }
0037 
0038 void *ATOM::Feed::role (RoleType msg, void *content)
0039 {
0040     if (RolePlaylist == msg)
0041         return !title.isEmpty () ? (PlaylistRole *) this : nullptr;
0042     return Element::role (msg, content);
0043 }
0044 
0045 Node *ATOM::Entry::childFromTag (const QString &tag) {
0046     QByteArray ba = tag.toLatin1 ();
0047     const char *cstr = ba.constData ();
0048     if (!strcmp (cstr, "link"))
0049         return new ATOM::Link (m_doc);
0050     else if (!strcmp (cstr, "content"))
0051         return new ATOM::Content (m_doc);
0052     else if (!strcmp (cstr, "title"))
0053         return new DarkNode (m_doc, tag.toUtf8 (), id_node_title);
0054     else if (!strcmp (cstr, "summary"))
0055         return new DarkNode (m_doc, tag.toUtf8 (), id_node_summary);
0056     else if (!strcmp (cstr, "media:group"))
0057         return new MediaGroup (m_doc);
0058     else if (!strcmp (cstr, "gd:rating"))
0059         return new DarkNode (m_doc, tag.toUtf8 (), id_node_gd_rating);
0060     else if (!strcmp (cstr, "category") ||
0061             !strcmp (cstr, "author:") ||
0062             !strcmp (cstr, "id") ||
0063             !strcmp (cstr, "updated") ||
0064             !strncmp (cstr, "yt:", 3) ||
0065             !strncmp (cstr, "gd:", 3))
0066         return new DarkNode (m_doc, tag.toUtf8 (), id_node_ignored);
0067     return nullptr;
0068 }
0069 
0070 void ATOM::Entry::closed () {
0071     MediaGroup *group = nullptr;
0072     Node *rating = nullptr;
0073     for (Node *c = firstChild (); c; c = c->nextSibling ())
0074         if (c->id == id_node_title) {
0075             title = c->innerText ().simplified ();
0076         } else if (c->id == id_node_gd_rating) {
0077             rating = c;
0078         } else if (c->id == id_node_media_group) {
0079             group = static_cast <MediaGroup *> (c);
0080         }
0081     if (group)
0082         group->addSummary (this, rating, QString(), QString(), QString(), 0, 0);
0083     Element::closed ();
0084 }
0085 
0086 void *ATOM::Entry::role (RoleType msg, void *content)
0087 {
0088     if (RolePlaylist == msg)
0089         return !title.isEmpty () ? (PlaylistRole *) this : nullptr;
0090     return Element::role (msg, content);
0091 }
0092 
0093 Node::PlayType ATOM::Link::playType () {
0094     return src.isEmpty () ? play_type_none : play_type_unknown;
0095 }
0096 
0097 void ATOM::Link::closed () {
0098     QString href;
0099     QString rel;
0100     for (Attribute *a = attributes ().first (); a; a = a->nextSibling ()) {
0101         if (a->name () == Ids::attr_href)
0102             href = a->value ();
0103         else if (a->name () == Ids::attr_title)
0104             title = a->value ();
0105         else if (a->name () == "rel")
0106             rel = a->value ();
0107     }
0108     if (!href.isEmpty () && rel == QString::fromLatin1 ("enclosure"))
0109         src = href;
0110     else if (title.isEmpty ())
0111         title = href;
0112     Mrl::closed ();
0113 }
0114 
0115 void ATOM::Content::closed () {
0116     for (Attribute *a = attributes ().first (); a; a = a->nextSibling ()) {
0117         if (a->name () == Ids::attr_src)
0118             src = a->value ();
0119         else if (a->name () == Ids::attr_type) {
0120             QString v = a->value ().toLower ();
0121             if (v == QString::fromLatin1 ("text"))
0122                 mimetype = QString::fromLatin1 ("text/plain");
0123             else if (v == QString::fromLatin1 ("html"))
0124                 mimetype = QString::fromLatin1 ("text/html");
0125             else if (v == QString::fromLatin1 ("xhtml"))
0126                 mimetype = QString::fromLatin1 ("application/xhtml+xml");
0127             else
0128                 mimetype = v;
0129         }
0130     }
0131     Mrl::closed ();
0132 }
0133 
0134 Node::PlayType ATOM::Content::playType () {
0135     if (!hasChildNodes () && !src.isEmpty ())
0136         return play_type_unknown;
0137     return play_type_none;
0138 }
0139 
0140 Node *ATOM::MediaGroup::childFromTag (const QString &tag) {
0141     QByteArray ba = tag.toLatin1 ();
0142     const char *cstr = ba.constData ();
0143     if (!strcmp (cstr, "media:content"))
0144         return new ATOM::MediaContent (m_doc);
0145     else if (!strcmp (cstr, "media:title"))
0146         return new DarkNode (m_doc, tag.toUtf8 (), id_node_media_title);
0147     else if (!strcmp (cstr, "media:description"))
0148         return new DarkNode (m_doc, tag.toUtf8 (), id_node_media_description);
0149     else if (!strcmp (cstr, "media:thumbnail"))
0150         return new DarkNode (m_doc, tag.toUtf8 (), id_node_media_thumbnail);
0151     else if (!strcmp (cstr, "media:player"))
0152         return new DarkNode (m_doc, tag.toUtf8 (), id_node_media_player);
0153     else if (!strcmp (cstr, "media:category") ||
0154             !strcmp (cstr, "media:keywords") ||
0155             !strcmp (cstr, "media:credit"))
0156         return new DarkNode (m_doc, tag.toUtf8 (), id_node_ignored);
0157     else if (!strcmp (cstr, "smil"))
0158         return new SMIL::Smil (m_doc);
0159     return nullptr;
0160 }
0161 
0162 void ATOM::MediaGroup::message (MessageType msg, void *content) {
0163     if (MsgChildFinished == msg &&
0164             id_node_media_content == ((Posting *) content)->source->id)
0165         finish (); // only play one
0166     Element::message (msg, content);
0167 }
0168 
0169 static QString makeStar (int x, bool fill) {
0170     QString path = "<path style=\"stroke:#A0A0A0;stroke-width:2px;stroke-opacity:1;";
0171     if (fill)
0172         path += "fill:#ff0000";
0173     else
0174         path += "fill:#C0C0C0";
0175     path += "\" d=\"M 21.428572,23.571429 "
0176         "L 10.84984,18.213257 L 0.43866021,23.890134 L 2.2655767,12.173396 "
0177         "L -6.3506861,4.0260275 L 5.3571425,2.142857 L 10.443179,-8.5693712 "
0178         "L 15.852098,1.9835038 L 27.611704,3.5103513 L 19.246772,11.915557 "
0179         "L 21.428572,23.571429 z\""
0180         " transform=\"translate(";
0181     path += QString::number (x);
0182     path += ",11)\"/>";
0183     return path;
0184 }
0185 
0186 static QString makeImage(const QString& url, int width, int height) {
0187     QString str = QString ("<img region=\"image\" src=\"") + url + QChar ('"');
0188     if (width && height) {
0189         str += QString(" width=\"%1\" height=\"%2\"").arg(width).arg(height);
0190     }
0191     str += QString (" dur=\"20\" transIn=\"fade\" fill=\"transition\" fit=\"meet\"/>");
0192     return str;
0193 }
0194 
0195 //http://code.google.com/apis/youtube/2.0/developers_guide_protocol.html
0196 void ATOM::MediaGroup::addSummary(Node *p, Node *rating_node,
0197         const QString& alt_title, const QString& alt_desc, const QString& alt_img, int width, int height) {
0198     QString images;
0199     QString desc;
0200     QString title;
0201     QString player;
0202     QString ratings;
0203     int img_count = 0;
0204     if (rating_node) {
0205         Element *e = static_cast <Element *> (rating_node);
0206         QString nr = e->getAttribute ("average");
0207         if (!nr.isEmpty ()) {
0208             int rating = ((int) nr.toDouble ()) % 6;
0209             ratings = "<img region=\"rating\">"
0210                 "<svg width=\"200\" height=\"40\">";
0211             for (int i = 0; i < 5; ++i)
0212                 ratings += makeStar (10 + i * 40, rating > i);
0213             ratings += "</svg></img>";
0214         }
0215     }
0216     for (Node *c = firstChild (); c; c = c->nextSibling ()) {
0217         switch (c->id) {
0218         case id_node_media_title:
0219             title = c->innerText ();
0220             break;
0221         case id_node_media_description:
0222             desc = c->innerText ();
0223             break;
0224         case id_node_media_player:
0225             player = static_cast <Element *> (c)->getAttribute (Ids::attr_url);
0226             break;
0227         case id_node_media_thumbnail:
0228         {
0229             Element *e = static_cast <Element *> (c);
0230             QString url = e->getAttribute (Ids::attr_url);
0231             if (!url.isEmpty ()) {
0232                 images += makeImage(url, e->getAttribute (Ids::attr_width).toInt(),
0233                                          e->getAttribute (Ids::attr_height).toInt());
0234                 img_count++;
0235             }
0236             break;
0237         }
0238         }
0239     }
0240     if (title.isEmpty())
0241         title = alt_title;
0242     if (desc.isEmpty())
0243         desc = alt_desc;
0244     if (!img_count && !alt_img.isEmpty()) {
0245         images = makeImage(alt_img, width, height);
0246         ++img_count;
0247     }
0248     if (img_count) {
0249         QString buf;
0250         QTextStream out (&buf, QIODevice::WriteOnly);
0251         out << "<smil><head>";
0252         if (!title.isEmpty ())
0253             out << "<title>" << title << "</title>";
0254         out << "<layout><root-layout width=\"400\" height=\"300\" background-color=\"#F5F5DC\"/>";
0255         if (!title.isEmpty ())
0256             out << "<region id=\"title\" left=\"20\" top=\"10\" height=\"18\" right=\"10\"/>";
0257         out << "<region id=\"image\" left=\"5\" top=\"40\" width=\"130\" bottom=\"30\"/>";
0258         if (!ratings.isEmpty ())
0259             out << "<region id=\"rating\" left=\"15\" width=\"100\" height=\"20\" bottom=\"5\"/>";
0260         out << "<region id=\"text\" left=\"140\" top=\"40\" bottom=\"10\" right=\"10\" fit=\"scroll\"/>"
0261             "</layout>"
0262             "<transition id=\"fade\" dur=\"0.3\" type=\"fade\"/>"
0263             "</head><body>"
0264             "<par><seq repeatCount=\"indefinite\">";
0265         out << images;
0266         out << "</seq>";
0267         if (!title.isEmpty ()) {
0268             if (!player.isEmpty ())
0269                 out << "<a href=\"" << XMLStringlet(player) << "\" target=\"top\">";
0270             out << "<smilText region=\"title\" textFontWeight=\"bold\" textFontSize=\"11\"";
0271             if (!player.isEmpty ())
0272                 out << " textColor=\"blue\"";
0273             out << ">" << XMLStringlet (title) << "</smilText>";
0274             if (!player.isEmpty ())
0275                 out << "</a>";
0276         }
0277         if (!ratings.isEmpty ())
0278             out << ratings;
0279         out << "<smilText region=\"text\" textFontFamily=\"serif\" textFontSize=\"11\">";
0280         out << XMLStringlet (desc);
0281         out << QString ("</smilText></par></body></smil>");
0282         QTextStream inxml (&buf, QIODevice::ReadOnly);
0283         KMPlayer::readXML (this, inxml, QString (), false);
0284         NodePtr n = lastChild();
0285         n->normalize ();
0286         n->auxiliary_node = true;
0287         removeChild (n);
0288         p->insertBefore (n, p->firstChild ());
0289     }
0290 }
0291 
0292 void ATOM::MediaContent::closed () {
0293     unsigned fsize = 0;
0294     unsigned bitrate = 0;
0295     TrieString fs ("fileSize");
0296     TrieString rate ("bitrate");
0297     for (Attribute *a = attributes ().first (); a; a = a->nextSibling ()) {
0298         if (a->name () == Ids::attr_url)
0299             src = a->value();
0300         else if (a->name () == Ids::attr_type)
0301             mimetype = a->value ();
0302         else if (a->name () == Ids::attr_height)
0303             size.height = a->value ().toInt ();
0304         else if (a->name () == Ids::attr_width)
0305             size.width = a->value ().toInt ();
0306         else if (a->name () == Ids::attr_width)
0307             size.width = a->value ().toInt ();
0308         else if (a->name () == fs)
0309             fsize = a->value ().toInt ();
0310         else if (a->name () == rate)
0311             bitrate = a->value ().toInt ();
0312     }
0313     if (!mimetype.isEmpty ()) {
0314         title = mimetype;
0315         if (fsize > 0) {
0316             if (fsize > 1024 * 1024)
0317                 title += QString (" (%1 Mb)").arg (fsize / (1024 * 1024));
0318             else
0319                 title += QString (" (%1 kb)").arg (fsize / 1024);
0320         } else if (bitrate > 0) {
0321             if (bitrate > 10 * 1024)
0322                 title += QString(" (%1 Mbit/s)").arg(bitrate / 1024);
0323             else
0324                 title += QString(" (%1 kbit/s)").arg(bitrate);
0325         }
0326     }
0327     Mrl::closed ();
0328 }