1 | /* smplayer, GUI front-end for mplayer.
|
---|
2 | Copyright (C) 2006-2013 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 | RetrieveYoutubeUrl::RetrieveYoutubeUrl( QObject* parent ) : QObject(parent)
|
---|
28 | {
|
---|
29 | reply = 0;
|
---|
30 | manager = new QNetworkAccessManager(this);
|
---|
31 | connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(gotResponse(QNetworkReply*)));
|
---|
32 |
|
---|
33 | preferred_quality = FLV_360p;
|
---|
34 | user_agent = "Mozilla/5.0 (X11; Linux x86_64; rv:5.0.1) Gecko/20100101 Firefox/5.0.1";
|
---|
35 | }
|
---|
36 |
|
---|
37 | RetrieveYoutubeUrl::~RetrieveYoutubeUrl() {
|
---|
38 | }
|
---|
39 |
|
---|
40 | void RetrieveYoutubeUrl::fetchPage(const QString & url) {
|
---|
41 | QNetworkRequest req(url);
|
---|
42 | req.setRawHeader("User-Agent", user_agent.toLatin1());
|
---|
43 | reply = manager->get(req);
|
---|
44 | orig_url = url;
|
---|
45 |
|
---|
46 | emit connecting(url);
|
---|
47 | }
|
---|
48 |
|
---|
49 | void RetrieveYoutubeUrl::close() {
|
---|
50 | if (reply) reply->abort();
|
---|
51 | }
|
---|
52 |
|
---|
53 | void RetrieveYoutubeUrl::gotResponse(QNetworkReply* reply) {
|
---|
54 | if (reply->error() == QNetworkReply::NoError) {
|
---|
55 | int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
---|
56 | qDebug("RetrieveYoutubeUrl::gotResponse: status: %d", status);
|
---|
57 | switch (status) {
|
---|
58 | case 301:
|
---|
59 | case 302:
|
---|
60 | case 307:
|
---|
61 | QString r_url = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl().toString();
|
---|
62 | qDebug("RetrieveYoutubeUrl::gotResponse: redirected: %s", r_url.toLatin1().constData());
|
---|
63 | fetchPage(r_url);
|
---|
64 | return;
|
---|
65 | }
|
---|
66 | } else {
|
---|
67 | emit errorOcurred((int)reply->error(), reply->errorString());
|
---|
68 | return;
|
---|
69 | }
|
---|
70 | parse(reply->readAll());
|
---|
71 | }
|
---|
72 |
|
---|
73 | void RetrieveYoutubeUrl::parse(QByteArray text) {
|
---|
74 | qDebug("RetrieveYoutubeUrl::parse");
|
---|
75 |
|
---|
76 | urlMap.clear();
|
---|
77 |
|
---|
78 | QString replyString = QString::fromUtf8(text);
|
---|
79 |
|
---|
80 | QRegExp rx_title(".*<title>(.*)</title>.*");
|
---|
81 | if (rx_title.indexIn(replyString) != -1) {
|
---|
82 | url_title = rx_title.cap(1).simplified();
|
---|
83 | url_title = QString(url_title).replace("&","&").replace(">", ">").replace("<", "<").replace(""","\"").replace("'","'")/*.replace(" - YouTube", "")*/;
|
---|
84 | qDebug("RetrieveYoutubeUrl::parse: title '%s'", url_title.toUtf8().constData());
|
---|
85 | } else {
|
---|
86 | url_title = "Youtube video";
|
---|
87 | }
|
---|
88 |
|
---|
89 | QRegExp regex("\\\"url_encoded_fmt_stream_map\\\"\\s*:\\s*\\\"([^\\\"]*)");
|
---|
90 | regex.indexIn(replyString);
|
---|
91 | QString fmtArray = regex.cap(1);
|
---|
92 | fmtArray = sanitizeForUnicodePoint(fmtArray);
|
---|
93 | fmtArray.replace(QRegExp("\\\\(.)"), "\\1");
|
---|
94 |
|
---|
95 | bool signature_not_found = false;
|
---|
96 |
|
---|
97 | QList<QByteArray> codeList = fmtArray.toLatin1().split(',');
|
---|
98 | foreach(QByteArray code, codeList) {
|
---|
99 | code = QUrl::fromPercentEncoding(code).toLatin1();
|
---|
100 | //qDebug("code: %s", code.constData());
|
---|
101 |
|
---|
102 | QUrl line;
|
---|
103 | line.setEncodedQuery(code);
|
---|
104 |
|
---|
105 | if (line.hasQueryItem("url")) {
|
---|
106 | QUrl url( line.queryItemValue("url") );
|
---|
107 | line.setScheme(url.scheme());
|
---|
108 | line.setHost(url.host());
|
---|
109 | line.setPath(url.path());
|
---|
110 | line.setEncodedQuery( line.encodedQuery() + "&" + url.encodedQuery() );
|
---|
111 | line.removeQueryItem("url");
|
---|
112 |
|
---|
113 | if (line.hasQueryItem("sig")) {
|
---|
114 | line.addQueryItem("signature", line.queryItemValue("sig"));
|
---|
115 | line.removeQueryItem("sig");
|
---|
116 | }
|
---|
117 | else
|
---|
118 | if (line.hasQueryItem("s")) {
|
---|
119 | QString signature = YTSig::aclara(line.queryItemValue("s"));
|
---|
120 | if (!signature.isEmpty()) {
|
---|
121 | line.addQueryItem("signature", signature);
|
---|
122 | } else {
|
---|
123 | signature_not_found = true;
|
---|
124 | }
|
---|
125 | line.removeQueryItem("s");
|
---|
126 | }
|
---|
127 | line.removeAllQueryItems("fallback_host");
|
---|
128 | line.removeAllQueryItems("type");
|
---|
129 | if ((line.hasQueryItem("itag")) && (line.hasQueryItem("signature"))) {
|
---|
130 | QString itag = line.queryItemValue("itag");
|
---|
131 | line.removeAllQueryItems("itag"); // Remove duplicated itag
|
---|
132 | line.addQueryItem("itag", itag);
|
---|
133 | urlMap[itag.toInt()] = line.toString();
|
---|
134 | //qDebug("line: %s", line.toString().toLatin1().constData());
|
---|
135 | }
|
---|
136 | }
|
---|
137 | }
|
---|
138 |
|
---|
139 | qDebug("RetrieveYoutubeUrl::parse: url count: %d", urlMap.count());
|
---|
140 |
|
---|
141 | if ((urlMap.count() == 0) && (signature_not_found)) {
|
---|
142 | qDebug("RetrieveYoutubeUrl::parse: no url found with valid signature");
|
---|
143 | emit signatureNotFound(url_title);
|
---|
144 | return;
|
---|
145 | }
|
---|
146 |
|
---|
147 | QString p_url = findPreferredUrl();
|
---|
148 | //qDebug("p_url: '%s'", p_url.toLatin1().constData());
|
---|
149 |
|
---|
150 | if (!p_url.isNull()) {
|
---|
151 | emit gotUrls(urlMap);
|
---|
152 | emit gotPreferredUrl(p_url);
|
---|
153 | } else {
|
---|
154 | emit gotEmptyList();
|
---|
155 | }
|
---|
156 | }
|
---|
157 |
|
---|
158 | QString RetrieveYoutubeUrl::findPreferredUrl() {
|
---|
159 | latest_preferred_url = findPreferredUrl(urlMap, preferred_quality);
|
---|
160 | return latest_preferred_url;
|
---|
161 | }
|
---|
162 |
|
---|
163 | QString RetrieveYoutubeUrl::findPreferredUrl(const QMap<int, QString>& urlMap, Quality q) {
|
---|
164 | // Choose a url according to preferred quality
|
---|
165 | QString p_url;
|
---|
166 | //Quality q = preferred_quality;
|
---|
167 |
|
---|
168 | if (q==MP4_1080p) {
|
---|
169 | p_url = urlMap.value(MP4_1080p, QString());
|
---|
170 | if (p_url.isNull()) p_url= urlMap.value(WEBM_1080p, QString());
|
---|
171 | if (p_url.isNull()) q = MP4_720p;
|
---|
172 | }
|
---|
173 |
|
---|
174 | if (q==WEBM_1080p) {
|
---|
175 | p_url = urlMap.value(WEBM_1080p, QString());
|
---|
176 | if (p_url.isNull()) p_url= urlMap.value(MP4_1080p, QString());
|
---|
177 | if (p_url.isNull()) q = WEBM_720p;
|
---|
178 | }
|
---|
179 |
|
---|
180 | if (q==MP4_720p) {
|
---|
181 | p_url = urlMap.value(MP4_720p, QString());
|
---|
182 | if (p_url.isNull()) p_url= urlMap.value(WEBM_720p, QString());
|
---|
183 | if (p_url.isNull()) p_url = urlMap.value(WEBM_480p, QString());
|
---|
184 | if (p_url.isNull()) q = MP4_360p;
|
---|
185 | }
|
---|
186 |
|
---|
187 | if (q==WEBM_720p) {
|
---|
188 | p_url = urlMap.value(WEBM_720p, QString());
|
---|
189 | if (p_url.isNull()) p_url= urlMap.value(MP4_720p, QString());
|
---|
190 | if (p_url.isNull()) q = WEBM_480p;
|
---|
191 | }
|
---|
192 |
|
---|
193 | if (q==WEBM_480p) {
|
---|
194 | p_url = urlMap.value(WEBM_480p, QString());
|
---|
195 | if (p_url.isNull()) q = WEBM_360p;
|
---|
196 | }
|
---|
197 |
|
---|
198 | if (q==MP4_360p) {
|
---|
199 | p_url = urlMap.value(MP4_360p, QString());
|
---|
200 | if (p_url.isNull()) p_url= urlMap.value(WEBM_360p, QString());
|
---|
201 | if (p_url.isNull()) q = FLV_360p;
|
---|
202 | }
|
---|
203 |
|
---|
204 | if (q==WEBM_360p) {
|
---|
205 | p_url = urlMap.value(WEBM_360p, QString());
|
---|
206 | if (p_url.isNull()) p_url= urlMap.value(MP4_360p, QString());
|
---|
207 | if (p_url.isNull()) q = FLV_360p;
|
---|
208 | }
|
---|
209 |
|
---|
210 | // FLV, low priority
|
---|
211 | if (q==FLV_480p) {
|
---|
212 | p_url = urlMap.value(FLV_480p, QString());
|
---|
213 | if (p_url.isNull()) q = FLV_360p;
|
---|
214 | }
|
---|
215 |
|
---|
216 | if (q==FLV_360p) {
|
---|
217 | p_url = urlMap.value(FLV_360p, QString());
|
---|
218 | if (p_url.isNull()) q = FLV_240p;
|
---|
219 | }
|
---|
220 |
|
---|
221 | if (q==FLV_240p) {
|
---|
222 | p_url = urlMap.value(q, QString());
|
---|
223 | }
|
---|
224 |
|
---|
225 | return p_url;
|
---|
226 | }
|
---|
227 |
|
---|
228 | QString RetrieveYoutubeUrl::sanitizeForUnicodePoint(QString string) {
|
---|
229 | QRegExp rx("\\\\u(\\d{4})");
|
---|
230 | while (rx.indexIn(string) != -1) {
|
---|
231 | string.replace(rx.cap(0), QString(QChar(rx.cap(1).toInt(0,16))));
|
---|
232 | }
|
---|
233 | return string;
|
---|
234 | }
|
---|
235 |
|
---|
236 | void RetrieveYoutubeUrl::htmlDecode(QString& string) {
|
---|
237 | string.replace("%3A", ":", Qt::CaseInsensitive);
|
---|
238 | string.replace("%2F", "/", Qt::CaseInsensitive);
|
---|
239 | string.replace("%3F", "?", Qt::CaseInsensitive);
|
---|
240 | string.replace("%3D", "=", Qt::CaseInsensitive);
|
---|
241 | string.replace("%25", "%", Qt::CaseInsensitive);
|
---|
242 | string.replace("%26", "&", Qt::CaseInsensitive);
|
---|
243 | string.replace("%3D", "=", Qt::CaseInsensitive);
|
---|
244 | }
|
---|
245 |
|
---|
246 | #include "moc_retrieveyoutubeurl.cpp"
|
---|