media/youtube.lua: Use HTTPS if input URL uses it
[libquvi-scripts.git] / share / media / youtube.lua
blob52aa7253b7bfce48a069e1bd4fe30c0f16f90b04
1 -- libquvi-scripts
2 -- Copyright (C) 2010-2013 Toni Gundogdu <legatvs@gmail.com>
3 --
4 -- This file is part of libquvi-scripts <http://quvi.sourceforge.net/>.
5 --
6 -- This program is free software: you can redistribute it and/or
7 -- modify it under the terms of the GNU Affero General Public
8 -- License as published by the Free Software Foundation, either
9 -- version 3 of the License, or (at your option) any later version.
11 -- This program is distributed in the hope that it will be useful,
12 -- but WITHOUT ANY WARRANTY; without even the implied warranty of
13 -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 -- GNU Affero General Public License for more details.
16 -- You should have received a copy of the GNU Affero General
17 -- Public License along with this program. If not, see
18 -- <http://www.gnu.org/licenses/>.
21 local YouTube = {} -- Utility functions unique to this script
23 -- <http://en.wikipedia.org/wiki/YouTube#Quality_and_codecs>
25 -- Identify the script.
26 function ident(qargs)
27 local Y = require 'quvi/youtube'
28 return Y.ident(qargs)
29 end
31 -- Parse media properties.
32 function parse(qargs)
33 return YouTube.parse_properties(qargs)
34 end
37 -- Utility functions
40 -- Make a new get_view_info request.
41 function YouTube.gvi_request_new(qargs, input_url)
42 local U = require 'socket.url'
43 local u = U.parse(input_url)
45 qargs.id = u.query:match('v=([%w-_]+)') or error('no match: media ID')
47 local t = {
48 u.scheme, '://www.youtube.com/get_video_info?', 'video_id=', qargs.id,
49 '&el=detailpage', '&ps=default', '&gl=US', '&hl=en'
52 local T = require 'quvi/util'
53 local r = table.concat(t,'')
55 local c = T.decode(quvi.http.fetch(r).data)
56 local r = c['reason']
58 if r and #r then
59 error(string.format("%s (errorcode=%s)",
60 U.unescape(r), c['errorcode']))
61 end
62 return c, U, T, u.scheme
63 end
65 -- Parse the video info from the server.
66 function YouTube.parse_properties(qargs)
67 local Y = require 'quvi/youtube'
68 local u = Y.normalize(qargs.input_url)
70 local c,U,T,scheme = YouTube.gvi_request_new(qargs, u)
72 qargs.duration_ms = (c['length_seconds'] or 0)*1000 -- to ms
73 qargs.thumb_url = T.unescape(c['thumbnail_url'] or '')
75 qargs.title = T.unescape(c['title'] or '')
76 qargs.streams = YouTube.iter_streams(c, U, T, scheme)
78 YouTube.append_begin_param(qargs, U)
79 return qargs
80 end
82 -- Append the &begin parameter to the media stream URL.
83 function YouTube.append_begin_param(qargs, U)
84 local m,s = qargs.input_url:match('t=(%d?%d?m?)(%d%d)s')
85 m = tonumber(((m or ''):gsub('%a',''))) or 0
86 s = tonumber(((s or ''):gsub('%a',''))) or 0
87 local ms = (m*60000) + (s*1000)
88 if ms >0 then -- Rebuild each stream URL with the 'begin' parameter.
89 for i,v in ipairs(qargs.streams) do
90 local u = U.parse(qargs.streams[i].url)
91 u.query = table.concat({u.query, '&begin=', ms}, '')
92 qargs.streams[i].url = U.build(u)
93 end
94 qargs.start_time_ms = ms
95 end
96 end
98 -- Return a new media stream URL.
99 function YouTube.stream_url_new(d, U, T, scheme)
100 local u = U.parse(T.unescape(d['url']))
102 -- The service returns only HTTP media stream URLs even if the media
103 -- properties were requested over HTTPS. Forcing HTTPS will only
104 -- result in HTTP/403. (2013-09-11)
106 --u.scheme = scheme -- Uncomment to use the input URL scheme
108 if d['sig'] then
109 local s = table.concat({'&signature=', T.unescape(d['sig'])}, '')
110 u.query = table.concat({u.query, s}, '')
112 return U.build(u) -- Rebuild the stream URL.
115 -- Iterate the available streams.
116 function YouTube.iter_streams(config, U, T, scheme)
118 -- stream_map: holds many of the essential properties.
119 local v = 'url_encoded_fmt_stream_map'
120 local stream_map = T.unescape(config[v]
121 or error(string.format('no match: %s', v)))
122 .. ','
124 local smr = {}
125 for d in stream_map:gmatch('([^,]*),') do
126 local d = T.decode(d)
127 if d['url'] then -- Found media stream URL.
128 local ct = T.unescape(d['type'])
129 local v_enc, a_enc = ct:match('codecs="([%w.]+),%s+([%w.]+)"')
130 local t = {
131 container = (ct:match('/([%w-]+)')):gsub('x%-', ''),
132 url = YouTube.stream_url_new(d, U, T, scheme),
133 quality = d['quality'],
134 v_enc = v_enc,
135 a_enc = a_enc
137 local itag = d['itag']
138 smr[itag] = t
142 -- fmt_list: stores the video resolutions.
143 local fmtl = T.unescape(config['fmt_list'] or error('no match: fmt_list'))
145 local S = require 'quvi/stream'
146 local r = {}
148 for itag,w,h in fmtl:gmatch('(%d+)/(%d+)x(%d+)') do
149 local smri = smr[itag]
150 local t = S.stream_new(smri.url)
152 t.video.encoding = smri.v_enc or ''
153 t.audio.encoding = smri.a_enc or ''
155 t.container = smri.container or ''
157 t.video.height = tonumber(h)
158 t.video.width = tonumber(w)
160 t.id = YouTube.to_id(t, itag, smri)
161 table.insert(r, t)
164 if #r >1 then -- Pick one stream as the 'best' quality.
165 YouTube.ch_best(S, r)
167 return r
170 -- Choose the stream with the highest video height property as the best.
171 function YouTube.ch_best(S, t)
172 local r = t[1] -- Make the first one the 'best' by default.
173 r.flags.best = true
174 for _,v in pairs(t) do
175 if v.video.height > r.video.height then
176 r = S.swap_best(r, v)
181 -- Return an ID for a stream.
182 function YouTube.to_id(t, itag, smri)
183 return string.format("%s_%s_i%02d_%sp",
184 smri.quality, t.container, itag, t.video.height)
187 -- vim: set ts=2 sw=2 tw=72 expandtab: