youtube_scrape_hq_mp4: test for availability of this format
[conkeror.git] / modules / page-modes / youtube.js
blob0fbfbb2785e28d99a38f9131e3499bec490a0845
1 /**
2  * (C) Copyright 2008 Jeremy Maitin-Shepard
3  * (C) Copyright 2009 John J. Foerch
4  *
5  * Use, modification, and distribution are subject to the terms specified in the
6  * COPYING file.
7 **/
9 require("content-buffer.js");
10 require("media.js");
12 let media_youtube_content_key_regexp = /"t": "([^"]+)"/;
13 let media_youtube_content_title_regexp = /<meta name="title" content="([^"]+)">/;
17  * Youtube Format Scrapers
18  *
19  *    Given a push function, the id of the video, the `t' key, and the
20  * text of the document (to scrape further data if necessary), the format
21  * scrapers' job is to call the push function with four args: url, file
22  * extension, mime type, and description.
23  *
24  *    Scrapers should return true on success and false on failure.
25  */
26 function youtube_scrape_standard_flv (push, id, t, text) {
27     push('http://youtube.com/get_video?video_id='+id+'&t='+t,
28          'flv', 'video/x-flv', 'standard flv');
29     return true;
32 function youtube_scrape_hq_mp4 (push, id, t, text) {
33     if (/"fmt_map": ""/.test(text))
34         return false;
35     push('http://youtube.com/get_video?video_id='+id+'&t='+t+'&fmt=18',
36          'mp4', 'video/mp4', 'hq mp4');
37     return true;
40 function youtube_scrape_720p_mp4 (push, id, t, text) {
41     if (!(/'IS_HD_AVAILABLE': true/.test(text)))
42         return false;
43     push('http://youtube.com/get_video?video_id='+id+'&t='+t+'&fmt=22',
44          'mp4', 'video/mp4', '720p mp4');
45     return true;
48 function youtube_scrape_1080p_mp4 (push, id, t, text) {
49     if (!(/"fmt_map": "37/.test(text)))
50         return false;
51     push('http://youtube.com/get_video?video_id='+id+'&t='+t+'&fmt=37',
52          'mp4', 'video/mp4', '1080p mp4');
57  * Scraper Composition
58  */
60 /**
61  * make_call_each takes any number of functions as arguments and returns a
62  * closure which, when called, calls each of those functions in order, and
63  * finally returns true if any of the functions returned true.
64  */
65 function make_call_each () {
66     var fns = Array.prototype.slice.call(arguments, 0);
67     return function () {
68         var args = Array.prototype.slice.call(arguments, 0);
69         var found = false;
70         fns.map(function (fn) {
71             if (fn.apply(this, args))
72                 found = true;
73         });
74         return found;
75     };
78 /**
79  * make_call_each_until_success takes any number of functions as arguments
80  * and returns a closure which, when called, calls each of those functions
81  * in order until one returns true.
82  */
83 function make_call_each_until_success () {
84     var fns = Array.prototype.slice.call(arguments, 0);
85     return function () {
86         var args = Array.prototype.slice.call(arguments, 0);
87         for each (var fn in fns) {
88             if (fn.apply(this, args))
89                 return true;
90         }
91         return false;
92     };
95 define_variable('youtube_scrape_function',
96                 make_call_each(youtube_scrape_1080p_mp4,
97                                youtube_scrape_720p_mp4,
98                                youtube_scrape_hq_mp4,
99                                youtube_scrape_standard_flv),
100     "This function is called as the last step of scraping a youtube page, "+
101     "after the basic information needed to build the media url has been "+
102     "extracted. Youtube_scape_function is called with four arguments: a "+
103     "`push' function, the id, the t key, and the text of the page.  Its "+
104     "job is to call the push function for each media url desired with "+
105     "the arguments url, file extension, mime type, and description.  It "+
106     "should return true if it called the push function at least once, and "+
107     "otherwise false.");
110 function youtube_scrape_text (results, frame, id, text) {
111     var title_match = media_youtube_content_title_regexp.exec(text);
112     if (!title_match)
113         return null;
114     var title = decodeURIComponent(title_match[1]);
115     var res = media_youtube_content_key_regexp.exec(text);
116     if (!res)
117         return null;
118     function push (url, extension, mime_type, description) {
119         results.push(load_spec({
120             uri: url,
121             suggest_filename_from_uri: false,
122             title: title,
123             filename_extension: extension,
124             source_frame: frame,
125             mime_type: mime_type,
126             description: description
127         }));
128     }
129     youtube_scrape_function(push, id, res[1], text);
132 function media_scrape_youtube (buffer, results) {
133     try {
134         var uri = buffer.current_uri.spec;
135         var result = media_youtube_uri_test_regexp.exec(uri);
136         if (!result)
137             return;
138         let text = buffer.document.documentElement.innerHTML;
139         let id = result[1];
140         youtube_scrape_text(results, buffer.top_frame, id, text);
141     } catch (e if !(e instanceof interactive_error)) {}
144 define_page_mode("youtube_mode",
145     $display_name = "YouTube",
146     $enable = function (buffer) {
147         media_setup_local_object_classes(buffer);
148     });
150 function media_scrape_embedded_youtube (buffer, results) {
151     const embedded_youtube_regexp = /^http:\/\/[a-zA-Z0-9\-.]+\.youtube\.com\/v\/(.*)$/;
152     for (let frame in frame_iterator(buffer.top_frame, buffer.focused_frame)) {
153         // Look for embedded YouTube videos
154         let obj_els = frame.document.getElementsByTagName("object");
155         for (let i = 0; i < obj_els.length; ++i) {
156             let obj_el = obj_els[i];
157             let param_els = obj_el.getElementsByTagName("param");
158             inner:
159             for (let j = 0; j < param_els.length; ++j) {
160                 let param_el = param_els[j];
161                 let match;
162                 if (param_el.getAttribute("name").toLowerCase() == "movie" &&
163                     (match = embedded_youtube_regexp.exec(param_el.getAttribute("value"))) != null) {
164                     try {
165                         let id = match[1];
166                         let lspec = load_spec({uri: "http://youtube.com/watch?v=" + id});
167                         var result =
168                             yield buffer.window.minibuffer.wait_for(
169                                 "Requesting " + lspec.uri + "...",
170                                 send_http_request(lspec));
171                         let text = result.responseText;
172                         if (text != null && text.length > 0)
173                             youtube_scrape_text(results, frame, id, text);
174                     } catch (e if (e instanceof abort)) {
175                         // Still allow other media scrapers to try even if the user aborted an http request,
176                         // but don't continue looking for embedded youtube videos.
177                         return;
178                     } catch (e) {
179                         // Some other error here means there was some problem with the request.
180                         // We'll just ignore it.
181                     }
182                     break inner;
183                 }
184             }
185         }
186     }
190 let media_youtube_uri_test_regexp = build_url_regex($domain = /(?:[a-z]+\.)?youtube/,
191                                                     $path = /watch\?v=([A-Za-z0-9\-_]+)/);
192 media_scrapers.unshift([/.*/, media_scrape_embedded_youtube]);
193 media_scrapers.unshift([media_youtube_uri_test_regexp, media_scrape_youtube]);
194 auto_mode_list.push([media_youtube_uri_test_regexp, youtube_mode]);