source: smplayer/trunk/src/youtube/retrieveyoutubeurl.cpp@ 165

Last change on this file since 165 was 165, checked in by Silvan Scherrer, 11 years ago

SMPlayer: update trunk to latest 0.8.7

File size: 14.8 KB
Line 
1/* smplayer, GUI front-end for mplayer.
2 Copyright (C) 2006-2014 Ricardo Villalba <rvm@users.sourceforge.net>
3 Copyright (C) 2010 Ori Rejwan
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18*/
19
20#include "retrieveyoutubeurl.h"
21#include <QUrl>
22#include <QRegExp>
23#include <QStringList>
24#include <QFile>
25#include "ytsig.h"
26
27#if QT_VERSION >= 0x050000
28#include <QUrlQuery>
29#endif
30
31#define USE_PLAYER_NAME
32
33QString RetrieveYoutubeUrl::user_agent;
34
35RetrieveYoutubeUrl::RetrieveYoutubeUrl( QObject* parent ) : QObject(parent)
36{
37 reply = 0;
38 manager = new QNetworkAccessManager(this);
39
40 preferred_quality = FLV_360p;
41}
42
43RetrieveYoutubeUrl::~RetrieveYoutubeUrl() {
44}
45
46void RetrieveYoutubeUrl::fetchPage(const QString & url) {
47 QString agent = user_agent;
48 if (agent.isEmpty()) agent = "Mozilla/5.0 (X11; Linux x86_64; rv:5.0.1) Gecko/20100101 Firefox/5.0.1";
49 qDebug("RetrieveYoutubeUrl::fetchPage: user agent: %s", agent.toLatin1().constData());
50
51 QNetworkRequest req(url);
52 req.setRawHeader("User-Agent", agent.toLatin1());
53 reply = manager->get(req);
54 connect(reply, SIGNAL(finished()), this, SLOT(gotResponse()));
55 orig_url = url;
56
57 emit connecting(QUrl(url).host());
58
59#ifdef YT_GET_VIDEOINFO
60 video_id = getVideoID(url);
61#endif
62}
63
64#ifdef YT_GET_VIDEOINFO
65void RetrieveYoutubeUrl::fetchVideoInfoPage() {
66 QString url = QString("http://www.youtube.com/get_video_info?el=detailpage&ps=default&eurl=&gl=US&hl=en&video_id=%1").arg(video_id);
67 //qDebug("RetrieveYoutubeUrl::fetchVideoInfoPage: url: %s", url.toUtf8().constData());
68
69 YTSig::check(url);
70 QNetworkRequest req(url);
71 req.setRawHeader("User-Agent", user_agent.toLatin1());
72 reply = manager->get(req);
73 connect(reply, SIGNAL(finished()), this, SLOT(gotVideoInfoResponse()));
74
75 emit connecting(QUrl(url).host());
76}
77#endif
78
79void RetrieveYoutubeUrl::close() {
80 if (reply) reply->abort();
81}
82
83void RetrieveYoutubeUrl::gotResponse() {
84 QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
85
86 if (reply->error() == QNetworkReply::NoError) {
87 int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
88 qDebug("RetrieveYoutubeUrl::gotResponse: status: %d", status);
89 switch (status) {
90 case 301:
91 case 302:
92 case 307:
93 QString r_url = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl().toString();
94 qDebug("RetrieveYoutubeUrl::gotResponse: redirected: %s", r_url.toLatin1().constData());
95 fetchPage(r_url);
96 return;
97 }
98 } else {
99 emit errorOcurred((int)reply->error(), reply->errorString());
100 return;
101 }
102 parse(reply->readAll());
103}
104
105#ifdef YT_GET_VIDEOINFO
106void RetrieveYoutubeUrl::gotVideoInfoResponse() {
107 qDebug("RetrieveYoutubeUrl::gotVideoInfoResponse");
108
109 QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
110
111 if (reply->error() != QNetworkReply::NoError) {
112 emit errorOcurred((int)reply->error(), reply->errorString());
113 return;
114 }
115 parseVideoInfo(reply->readAll());
116}
117#endif
118
119void RetrieveYoutubeUrl::parse(QByteArray text) {
120 qDebug("RetrieveYoutubeUrl::parse");
121
122 urlMap.clear();
123
124 QString replyString = QString::fromUtf8(text);
125
126 QRegExp rx_title(".*<title>(.*)</title>.*");
127 if (rx_title.indexIn(replyString) != -1) {
128 url_title = rx_title.cap(1).simplified();
129 url_title = QString(url_title).replace("&amp;","&").replace("&gt;", ">").replace("&lt;", "<").replace("&quot;","\"").replace("&#39;","'")/*.replace(" - YouTube", "")*/;
130 qDebug("RetrieveYoutubeUrl::parse: title '%s'", url_title.toUtf8().constData());
131 } else {
132 url_title = "Youtube video";
133 }
134
135 //qDebug("RetrieveYoutubeUrl::parse: replyString: %s",replyString.toLatin1().constData());
136
137 QString player;
138 QRegExp rxplayer("html5player-([\\d,\\w,-]+)\\.js");
139 if (rxplayer.indexIn(replyString) != -1) {
140 player = rxplayer.cap(1);
141 qDebug("RetrieveYoutubeUrl::parse: html5player: %s", player.toLatin1().constData());
142 }
143
144 QRegExp regex("\\\"url_encoded_fmt_stream_map\\\"\\s*:\\s*\\\"([^\\\"]*)");
145 regex.indexIn(replyString);
146 QString fmtArray = regex.cap(1);
147 fmtArray = sanitizeForUnicodePoint(fmtArray);
148 fmtArray.replace(QRegExp("\\\\(.)"), "\\1");
149
150#ifndef YT_GET_VIDEOINFO
151 bool failed_to_decrypt_signature = false;
152#endif
153
154 #if QT_VERSION >= 0x050000
155 QUrlQuery * q = new QUrlQuery();
156 #endif
157
158 QList<QByteArray> codeList = fmtArray.toLatin1().split(',');
159 foreach(QByteArray code, codeList) {
160 code = QUrl::fromPercentEncoding(code).toLatin1();
161 //qDebug("code: %s", code.constData());
162
163 QUrl line;
164 #if QT_VERSION >= 0x050000
165 q->setQuery(code);
166 #else
167 QUrl * q = &line;
168 q->setEncodedQuery(code);
169 #endif
170
171 if (q->hasQueryItem("url")) {
172 QUrl url( q->queryItemValue("url") );
173 line.setScheme(url.scheme());
174 line.setHost(url.host());
175 line.setPath(url.path());
176 q->removeQueryItem("url");
177 #if QT_VERSION >= 0x050000
178 q->setQuery( q->query(QUrl::FullyDecoded) + "&" + url.query(QUrl::FullyDecoded) );
179 /*
180 QList < QPair < QString,QString > > l = q->queryItems();
181 for (int n=0; n < l.count(); n++) {
182 qDebug("n: %d, %s = %s", n, l[n].first.toLatin1().constData(), l[n].second.toLatin1().constData());
183 }
184 */
185 #else
186 q->setEncodedQuery( q->encodedQuery() + "&" + url.encodedQuery() );
187 /*
188 QList < QPair < QString,QString > > l = q->queryItems();
189 for (int n=0; n < l.count(); n++) {
190 qDebug("n: %d, %s = %s", n, l[n].first.toLatin1().constData(), l[n].second.toLatin1().constData());
191 }
192 */
193 #endif
194
195 if (q->hasQueryItem("sig")) {
196 q->addQueryItem("signature", q->queryItemValue("sig"));
197 q->removeQueryItem("sig");
198 }
199 else
200 if (q->hasQueryItem("s")) {
201 #ifdef USE_PLAYER_NAME
202 QString signature = YTSig::aclara(q->queryItemValue("s"), player);
203 #else
204 QString signature = YTSig::aclara(q->queryItemValue("s"));
205 #endif
206 if (!signature.isEmpty()) {
207 q->addQueryItem("signature", signature);
208 } else {
209 #ifndef YT_GET_VIDEOINFO
210 failed_to_decrypt_signature = true;
211 #endif
212 }
213 q->removeQueryItem("s");
214 }
215 q->removeAllQueryItems("fallback_host");
216 q->removeAllQueryItems("type");
217
218 if ((q->hasQueryItem("itag")) && (q->hasQueryItem("signature"))) {
219 QString itag = q->queryItemValue("itag");
220 q->removeAllQueryItems("itag"); // Remove duplicated itag
221 q->addQueryItem("itag", itag);
222 #if QT_VERSION >= 0x050000
223 line.setQuery(q->query(QUrl::FullyDecoded));
224 #endif
225 urlMap[itag.toInt()] = line.toString();
226 //qDebug("line: %s", line.toString().toLatin1().constData());
227 }
228 }
229 }
230
231 #if QT_VERSION >= 0x050000
232 delete q;
233 #endif
234
235 qDebug("RetrieveYoutubeUrl::parse: url count: %d", urlMap.count());
236
237#ifndef YT_GET_VIDEOINFO
238 if ((urlMap.count() == 0) && (failed_to_decrypt_signature)) {
239 qDebug("RetrieveYoutubeUrl::parse: no url found with valid signature");
240 emit signatureNotFound(url_title);
241 return;
242 }
243#else
244 if (urlMap.count() == 0) {
245 qDebug("RetrieveYoutubeUrl::parse: no url found with valid signature");
246 fetchVideoInfoPage();
247 return;
248 }
249#endif
250
251 QString p_url = findPreferredUrl();
252 //qDebug("p_url: '%s'", p_url.toLatin1().constData());
253
254 if (!p_url.isNull()) {
255 emit gotUrls(urlMap);
256 emit gotPreferredUrl(p_url);
257 } else {
258 emit gotEmptyList();
259 }
260}
261
262QString RetrieveYoutubeUrl::getVideoID(QString video_url) {
263 if (video_url.contains("youtu.be/")) {
264 video_url.replace("youtu.be/", "youtube.com/watch?v=");
265 }
266 else
267 if (video_url.contains("y2u.be/")) {
268 video_url.replace("y2u.be/", "youtube.com/watch?v=");
269 }
270 else
271 if (video_url.contains("m.youtube.com")) {
272 video_url.replace("m.youtube.com", "www.youtube.com");
273 }
274
275 if ((video_url.startsWith("youtube.com")) || (video_url.startsWith("www.youtube.com"))) video_url = "http://" + video_url;
276
277 //qDebug("RetrieveYoutubeUrl::getVideoID: video_url: %s", video_url.toLatin1().constData());
278
279 QUrl url(video_url);
280
281 QString ID;
282
283#if QT_VERSION >= 0x050000
284 QUrlQuery * q = new QUrlQuery(url);
285#else
286 const QUrl * q = &url;
287#endif
288
289 if ((url.host().contains("youtube")) && (url.path().contains("watch_videos"))) {
290 if (q->hasQueryItem("video_ids")) {
291 int index = 0;
292 if (q->hasQueryItem("index")) index = q->queryItemValue("index").toInt();
293 QStringList list = q->queryItemValue("video_ids").split(",");
294 if (index < list.count()) ID = list[index];
295 }
296 }
297 else
298 if ((url.host().contains("youtube")) && (url.path().contains("watch"))) {
299 if (q->hasQueryItem("v")) {
300 ID = q->queryItemValue("v");
301 }
302 }
303
304#if QT_VERSION >= 0x050000
305 delete q;
306#endif
307
308 //qDebug("RetrieveYoutubeUrl::getVideoID: ID: %s", ID.toLatin1().constData());
309
310 return ID;
311}
312
313bool RetrieveYoutubeUrl::isUrlSupported(const QString & url) {
314 return (!getVideoID(url).isEmpty());
315}
316
317QString RetrieveYoutubeUrl::fullUrl(const QString & url) {
318 QString r;
319 QString ID = getVideoID(url);
320 if (!ID.isEmpty()) r = "http://www.youtube.com/watch?v=" + ID;
321 return r;
322}
323
324#ifdef YT_GET_VIDEOINFO
325void RetrieveYoutubeUrl::parseVideoInfo(QByteArray text) {
326 urlMap.clear();
327
328 #if QT_VERSION >= 0x050000
329 QUrlQuery all;
330 all.setQuery(text);
331 QByteArray fmtArray = all.queryItemValue("url_encoded_fmt_stream_map").toLatin1();
332 #else
333 QUrl all;
334 all.setEncodedQuery(text);
335 QByteArray fmtArray = all.queryItemValue("url_encoded_fmt_stream_map").toLatin1();
336 #endif
337
338 /*
339 qDebug("fmtArray: %s", fmtArray.constData());
340 return;
341 */
342
343 bool failed_to_decrypt_signature = false;
344
345 #if QT_VERSION >= 0x050000
346 QUrlQuery * q = new QUrlQuery();
347 #endif
348
349 QList<QByteArray> codeList = fmtArray.split(',');
350 foreach(QByteArray code, codeList) {
351 code = QUrl::fromPercentEncoding(code).toLatin1();
352 //qDebug("code: %s", code.constData());
353
354 QUrl line;
355 #if QT_VERSION >= 0x050000
356 q->setQuery(code);
357 #else
358 QUrl * q = &line;
359 q->setEncodedQuery(code);
360 #endif
361
362 if (q->hasQueryItem("url")) {
363 QUrl url( q->queryItemValue("url") );
364 line.setScheme(url.scheme());
365 line.setHost(url.host());
366 line.setPath(url.path());
367 q->removeQueryItem("url");
368 #if QT_VERSION >= 0x050000
369 q->setQuery( q->query(QUrl::FullyDecoded) + "&" + url.query(QUrl::FullyDecoded) );
370 /*
371 QList < QPair < QString,QString > > l = q->queryItems();
372 for (int n=0; n < l.count(); n++) {
373 qDebug("n: %d, %s = %s", n, l[n].first.toLatin1().constData(), l[n].second.toLatin1().constData());
374 }
375 */
376 #else
377 q->setEncodedQuery( q->encodedQuery() + "&" + url.encodedQuery() );
378 /*
379 QList < QPair < QString,QString > > l = q->queryItems();
380 for (int n=0; n < l.count(); n++) {
381 qDebug("n: %d, %s = %s", n, l[n].first.toLatin1().constData(), l[n].second.toLatin1().constData());
382 }
383 */
384 #endif
385
386 if (q->hasQueryItem("sig")) {
387 q->addQueryItem("signature", q->queryItemValue("sig"));
388 q->removeQueryItem("sig");
389 }
390 else
391 if (q->hasQueryItem("s")) {
392 QString signature = YTSig::aclara(q->queryItemValue("s"), "", "aclara_f");
393 if (!signature.isEmpty()) {
394 q->addQueryItem("signature", signature);
395 } else {
396 failed_to_decrypt_signature = true;
397 }
398 q->removeQueryItem("s");
399 }
400 q->removeAllQueryItems("fallback_host");
401 q->removeAllQueryItems("type");
402 if ((q->hasQueryItem("itag")) && (q->hasQueryItem("signature"))) {
403 QString itag = q->queryItemValue("itag");
404 q->removeAllQueryItems("itag"); // Remove duplicated itag
405 q->addQueryItem("itag", itag);
406 #if QT_VERSION >= 0x050000
407 line.setQuery(q->query(QUrl::FullyDecoded));
408 #endif
409 urlMap[itag.toInt()] = line.toString();
410 //qDebug("line: %s", line.toString().toLatin1().constData());
411 }
412 }
413 }
414
415 #if QT_VERSION >= 0x050000
416 delete q;
417 #endif
418
419 qDebug("RetrieveYoutubeUrl::parseVideoInfo: url count: %d", urlMap.count());
420
421 if ((urlMap.count() == 0) && (failed_to_decrypt_signature)) {
422 qDebug("RetrieveYoutubeUrl::parseVideoInfo: no url found with valid signature");
423 emit signatureNotFound(url_title);
424 return;
425 }
426
427 QString p_url = findPreferredUrl();
428 //qDebug("p_url: '%s'", p_url.toLatin1().constData());
429
430 if (!p_url.isNull()) {
431 emit gotUrls(urlMap);
432 emit gotPreferredUrl(p_url);
433 } else {
434 emit gotEmptyList();
435 }
436}
437#endif
438
439QString RetrieveYoutubeUrl::findPreferredUrl() {
440 latest_preferred_url = findPreferredUrl(urlMap, preferred_quality);
441 return latest_preferred_url;
442}
443
444QString RetrieveYoutubeUrl::findPreferredUrl(const QMap<int, QString>& urlMap, Quality q) {
445 // Choose a url according to preferred quality
446 QString p_url;
447 //Quality q = preferred_quality;
448
449 if (q==MP4_1080p) {
450 p_url = urlMap.value(MP4_1080p, QString());
451 if (p_url.isNull()) p_url= urlMap.value(WEBM_1080p, QString());
452 if (p_url.isNull()) q = MP4_720p;
453 }
454
455 if (q==WEBM_1080p) {
456 p_url = urlMap.value(WEBM_1080p, QString());
457 if (p_url.isNull()) p_url= urlMap.value(MP4_1080p, QString());
458 if (p_url.isNull()) q = WEBM_720p;
459 }
460
461 if (q==MP4_720p) {
462 p_url = urlMap.value(MP4_720p, QString());
463 if (p_url.isNull()) p_url= urlMap.value(WEBM_720p, QString());
464 if (p_url.isNull()) p_url = urlMap.value(WEBM_480p, QString());
465 if (p_url.isNull()) q = MP4_360p;
466 }
467
468 if (q==WEBM_720p) {
469 p_url = urlMap.value(WEBM_720p, QString());
470 if (p_url.isNull()) p_url= urlMap.value(MP4_720p, QString());
471 if (p_url.isNull()) q = WEBM_480p;
472 }
473
474 if (q==WEBM_480p) {
475 p_url = urlMap.value(WEBM_480p, QString());
476 if (p_url.isNull()) q = WEBM_360p;
477 }
478
479 if (q==MP4_360p) {
480 p_url = urlMap.value(MP4_360p, QString());
481 if (p_url.isNull()) p_url= urlMap.value(WEBM_360p, QString());
482 if (p_url.isNull()) q = FLV_360p;
483 }
484
485 if (q==WEBM_360p) {
486 p_url = urlMap.value(WEBM_360p, QString());
487 if (p_url.isNull()) p_url= urlMap.value(MP4_360p, QString());
488 if (p_url.isNull()) q = FLV_360p;
489 }
490
491 // FLV, low priority
492 if (q==FLV_480p) {
493 p_url = urlMap.value(FLV_480p, QString());
494 if (p_url.isNull()) q = FLV_360p;
495 }
496
497 if (q==FLV_360p) {
498 p_url = urlMap.value(FLV_360p, QString());
499 if (p_url.isNull()) q = FLV_240p;
500 }
501
502 if (q==FLV_240p) {
503 p_url = urlMap.value(q, QString());
504 }
505
506 return p_url;
507}
508
509QString RetrieveYoutubeUrl::sanitizeForUnicodePoint(QString string) {
510 QRegExp rx("\\\\u(\\d{4})");
511 while (rx.indexIn(string) != -1) {
512 string.replace(rx.cap(0), QString(QChar(rx.cap(1).toInt(0,16))));
513 }
514 return string;
515}
516
517void RetrieveYoutubeUrl::htmlDecode(QString& string) {
518 string.replace("%3A", ":", Qt::CaseInsensitive);
519 string.replace("%2F", "/", Qt::CaseInsensitive);
520 string.replace("%3F", "?", Qt::CaseInsensitive);
521 string.replace("%3D", "=", Qt::CaseInsensitive);
522 string.replace("%25", "%", Qt::CaseInsensitive);
523 string.replace("%26", "&", Qt::CaseInsensitive);
524 string.replace("%3D", "=", Qt::CaseInsensitive);
525}
526
527#include "moc_retrieveyoutubeurl.cpp"
Note: See TracBrowser for help on using the repository browser.