]> git.sur5r.net Git - minitube/blob - src/video.cpp
7b7a803de33d28ca07a891e0690cf9a9192d9a6b
[minitube] / src / video.cpp
1 #include "video.h"
2 #include "networkaccess.h"
3 #include <QtNetwork>
4 #include "videodefinition.h"
5
6 namespace The {
7     NetworkAccess* http();
8 }
9
10 Video::Video() : m_duration(0),
11 m_viewCount(-1),
12 definitionCode(0),
13 elIndex(0),
14 loadingStreamUrl(false)
15 { }
16
17 Video* Video::clone() {
18     Video* cloneVideo = new Video();
19     cloneVideo->m_title = m_title;
20     cloneVideo->m_description = m_description;
21     cloneVideo->m_author = m_author;
22     cloneVideo->m_webpage = m_webpage;
23     cloneVideo->m_streamUrl = m_streamUrl;
24     cloneVideo->m_thumbnail = m_thumbnail;
25     cloneVideo->m_thumbnailUrls = m_thumbnailUrls;
26     cloneVideo->m_duration = m_duration;
27     cloneVideo->m_published = m_published;
28     cloneVideo->m_viewCount = m_viewCount;
29     cloneVideo->videoId = videoId;
30     cloneVideo->videoToken = videoToken;
31     cloneVideo->definitionCode = definitionCode;
32     return cloneVideo;
33 }
34
35 void Video::preloadThumbnail() {
36     if (m_thumbnailUrls.isEmpty()) return;
37     QObject *reply = The::http()->get(m_thumbnailUrls.first());
38     connect(reply, SIGNAL(data(QByteArray)), SLOT(setThumbnail(QByteArray)));
39 }
40
41 void Video::setThumbnail(QByteArray bytes) {
42     m_thumbnail = QImage::fromData(bytes);
43     emit gotThumbnail();
44 }
45
46 const QImage Video::thumbnail() const {
47     return m_thumbnail;
48 }
49
50 void Video::loadStreamUrl() {
51     if (loadingStreamUrl) {
52         qDebug() << "Already loading stream URL for" << this->title();
53         return;
54     }
55     loadingStreamUrl = true;
56
57     // https://develop.participatoryculture.org/trac/democracy/browser/trunk/tv/portable/flashscraper.py
58
59     // Get Video ID
60     // youtube-dl line 428
61     // QRegExp re("^((?:http://)?(?:\\w+\\.)?youtube\\.com/(?:(?:v/)|(?:(?:watch(?:\\.php)?)?\\?(?:.+&)?v=)))?([0-9A-Za-z_-]+)(?(1).+)?$");
62     QRegExp re("^http://www\\.youtube\\.com/watch\\?v=([0-9A-Za-z_-]+).*");
63     bool match = re.exactMatch(m_webpage.toString());
64     if (!match || re.numCaptures() < 1) {
65         emit errorStreamUrl(QString("Cannot get video id for %1").arg(m_webpage.toString()));
66         loadingStreamUrl = false;
67         return;
68     }
69     videoId = re.cap(1);
70
71     getVideoInfo();
72
73 }
74
75 void  Video::getVideoInfo() {
76     static const QStringList elTypes = QStringList() << "&el=embedded" << "&el=vevo" << "&el=detailpage" << "";
77
78     if (elIndex > elTypes.size() - 1) {
79         // Don't panic! We have a plan B.
80         // get the youtube video webpage
81         qDebug() << "Scraping" << webpage().toString();
82         QObject *reply = The::http()->get(webpage().toString());
83         connect(reply, SIGNAL(data(QByteArray)), SLOT(scrapeWebPage(QByteArray)));
84         connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(errorVideoInfo(QNetworkReply*)));
85         // see you in scrapWebPage(QByteArray)
86         return;
87     }
88
89     // Get Video Token
90     QUrl videoInfoUrl = QUrl(QString(
91             "http://www.youtube.com/get_video_info?video_id=%1%2&ps=default&eurl=&gl=US&hl=en"
92             ).arg(videoId, elTypes.at(elIndex)));
93
94     QObject *reply = The::http()->get(videoInfoUrl);
95     connect(reply, SIGNAL(data(QByteArray)), SLOT(gotVideoInfo(QByteArray)));
96     connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(errorVideoInfo(QNetworkReply*)));
97
98     // see you in gotVideoInfo...
99
100 }
101
102 void  Video::gotVideoInfo(QByteArray data) {
103     QString videoInfo = QString::fromUtf8(data);
104
105     // qDebug() << "videoInfo" << videoInfo;
106
107     // get video token
108     QRegExp re = QRegExp("^.*&token=([^&]+).*$");
109     bool match = re.exactMatch(videoInfo);
110     // handle regexp failure
111     if (!match || re.numCaptures() < 1) {
112         // Don't panic! We're gonna try another magic "el" param
113         elIndex++;
114         getVideoInfo();
115         return;
116     }
117     QString videoToken = re.cap(1);
118     while (videoToken.contains('%'))
119         videoToken = QByteArray::fromPercentEncoding(videoToken.toAscii());
120     // qDebug() << "videoToken" << videoToken;
121     this->videoToken = videoToken;
122
123     // get fmt_url_map
124     re = QRegExp("^.*&fmt_url_map=([^&]+).*$");
125     match = re.exactMatch(videoInfo);
126     // handle regexp failure
127     if (!match || re.numCaptures() < 1) {
128         // Don't panic! We're gonna try another magic "el" param
129         elIndex++;
130         getVideoInfo();
131         return;
132     }
133
134     QString fmtUrlMap = re.cap(1);
135     fmtUrlMap = QByteArray::fromPercentEncoding(fmtUrlMap.toUtf8());
136
137     QSettings settings;
138     QString definitionName = settings.value("definition").toString();
139     int definitionCode = VideoDefinition::getDefinitionCode(definitionName);
140
141     // qDebug() << "fmtUrlMap" << fmtUrlMap;
142     QStringList formatUrls = fmtUrlMap.split(",", QString::SkipEmptyParts);
143     QHash<int, QString> urlMap;
144     foreach(QString formatUrl, formatUrls) {
145         int separator = formatUrl.indexOf("|");
146         if (separator == -1) continue;
147         int format = formatUrl.left(separator).toInt();
148         QString url = formatUrl.mid(separator + 1);
149
150         if (format == definitionCode) {
151             QUrl videoUrl = QUrl::fromEncoded(url.toUtf8(), QUrl::StrictMode);
152             m_streamUrl = videoUrl;
153             emit gotStreamUrl(videoUrl);
154             loadingStreamUrl = false;
155             return;
156         }
157
158         urlMap.insert(format, url);
159     }
160
161     QList<int> definitionCodes = VideoDefinition::getDefinitionCodes();
162     int currentIndex = definitionCodes.indexOf(definitionCode);
163     int previousIndex = 0;
164     while (currentIndex >= 0) {
165         previousIndex = currentIndex - 1;
166         int definitionCode = definitionCodes.at(previousIndex);
167         if (urlMap.contains(definitionCode)) {
168             qDebug() << "Found format" << definitionCode;
169             QString url = urlMap.value(definitionCode);
170             QUrl videoUrl = QUrl::fromEncoded(url.toUtf8(), QUrl::StrictMode);
171             m_streamUrl = videoUrl;
172             emit gotStreamUrl(videoUrl);
173             loadingStreamUrl = false;
174             return;
175         }
176         currentIndex--;
177     }
178
179     emit errorStreamUrl(tr("Cannot get video stream for %1").arg(m_webpage.toString()));
180
181 }
182
183 void Video::foundVideoUrl(QString videoToken, int definitionCode) {
184     // qDebug() << "foundVideoUrl" << videoToken << definitionCode;
185
186     QUrl videoUrl = QUrl(QString(
187             "http://www.youtube.com/get_video?video_id=%1&t=%2&eurl=&el=&ps=&asv=&fmt=%3"
188             ).arg(videoId, videoToken, QString::number(definitionCode)));
189
190     m_streamUrl = videoUrl;
191     emit gotStreamUrl(videoUrl);
192     loadingStreamUrl = false;
193 }
194
195 void Video::errorVideoInfo(QNetworkReply *reply) {
196     emit errorStreamUrl(tr("Network error: %1 for %2").arg(reply->errorString(), reply->url().toString()));
197     loadingStreamUrl = false;
198 }
199
200 void Video::scrapeWebPage(QByteArray data) {
201
202     QString videoHTML = QString::fromUtf8(data);
203     QRegExp re(".*, \"t\": \"([^\"]+)\".*");
204     bool match = re.exactMatch(videoHTML);
205
206     // on regexp failure, stop and report error
207     if (!match || re.numCaptures() < 1) {
208         emit errorStreamUrl("Error parsing video page");
209         loadingStreamUrl = false;
210         return;
211     }
212
213     QString videoToken = re.cap(1);
214     // FIXME proper decode
215     videoToken = videoToken.replace("%3D", "=");
216
217     // we'll need this in gotHeadHeaders()
218     this->videoToken = videoToken;
219
220     // qDebug() << "token" << videoToken;
221
222     QSettings settings;
223     QString definitionName = settings.value("definition").toString();
224     int definitionCode = VideoDefinition::getDefinitionCode(definitionName);
225     if (definitionCode == 18) {
226         // This is assumed always available
227         foundVideoUrl(videoToken, 18);
228     } else {
229         findVideoUrl(definitionCode);
230     }
231
232 }
233
234 void Video::gotHeadHeaders(QNetworkReply* reply) {
235     int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
236     // qDebug() << "gotHeaders" << statusCode;
237     if (statusCode == 200) {
238         foundVideoUrl(videoToken, definitionCode);
239     } else {
240
241         // try next (lower quality) definition
242         /*
243         QStringList definitionNames = VideoDefinition::getDefinitionNames();
244         int currentIndex = definitionNames.indexOf(currentDefinition);
245         int previousIndex = 0;
246         if (currentIndex > 0) {
247             previousIndex = currentIndex - 1;
248         }
249         if (previousIndex > 0) {
250             QString nextDefinitionName = definitionNames.at(previousIndex);
251             findVideoUrl(nextDefinitionName);
252         } else {
253             foundVideoUrl(videoToken, 18);
254         }*/
255
256
257         QList<int> definitionCodes = VideoDefinition::getDefinitionCodes();
258         int currentIndex = definitionCodes.indexOf(definitionCode);
259         int previousIndex = 0;
260         if (currentIndex > 0) {
261             previousIndex = currentIndex - 1;
262             int definitionCode = definitionCodes.at(previousIndex);
263             if (definitionCode == 18) {
264                 // This is assumed always available
265                 foundVideoUrl(videoToken, 18);
266             } else {
267                 findVideoUrl(definitionCode);
268             }
269
270         } else {
271             foundVideoUrl(videoToken, 18);
272         }
273
274     }
275 }
276
277 void Video::findVideoUrl(int definitionCode) {
278     this->definitionCode = definitionCode;
279
280     QUrl videoUrl = QUrl(QString(
281             "http://www.youtube.com/get_video?video_id=%1&t=%2&eurl=&el=&ps=&asv=&fmt=%3"
282             ).arg(videoId, videoToken, QString::number(definitionCode)));
283
284     QObject *reply = The::http()->head(videoUrl);
285     connect(reply, SIGNAL(finished(QNetworkReply*)), SLOT(gotHeadHeaders(QNetworkReply*)));
286     // connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(errorVideoInfo(QNetworkReply*)));
287
288     // see you in gotHeadHeaders()
289
290 }