Browse Source

youtube.lua: support new stream parameter data structure

Since at least last September, youtube has shown developments towards
discontinuing the classic parameters that we currently rely on in the
general case to play any video: the classic parameters are simply
missing at least sometimes for some videos.

Stream parameters remain available in another form under a new data
structure, which seems available all the time. It seems likely that the
classic parameters will in turn be phased out at some point.

For now, we introduce support for the new-style parameters while keeping
support for the classic ones.
pull/100/head
Pierre Ynard 7 years ago
parent
commit
3a12451bc2
  1. 84
      share/lua/playlist/youtube.lua

84
share/lua/playlist/youtube.lua

@ -219,7 +219,7 @@ function stream_url( params, js_url )
return url
end
-- Parse and pick our video URL
-- Parse and pick our video stream URL (classic parameters)
function pick_url( url_map, fmt, js_url )
for stream in string.gmatch( url_map, "[^,]+" ) do
local itag = string.match( stream, "itag=(%d+)" )
@ -230,6 +230,60 @@ function pick_url( url_map, fmt, js_url )
return nil
end
-- Parse and pick our video stream URL (new-style parameters)
function pick_stream( stream_map, js_url )
local pick = nil
local fmt = tonumber( get_url_param( vlc.path, "fmt" ) )
if fmt then
-- Legacy match from URL parameter
for stream in string.gmatch( stream_map, '{(.-)}' ) do
local itag = tonumber( string.match( stream, '"itag":(%d+)' ) )
if fmt == itag then
pick = stream
break
end
end
else
-- Compare the different available formats listed with our
-- quality targets
local prefres = vlc.var.inherit( nil, "preferred-resolution" )
local bestres = nil
for stream in string.gmatch( stream_map, '{(.-)}' ) do
local height = tonumber( string.match( stream, '"height":(%d+)' ) )
-- Better than nothing
if not pick or ( height and ( not bestres
-- Better quality within limits
or ( ( prefres < 0 or height <= prefres ) and height > bestres )
-- Lower quality more suited to limits
or ( prefres > -1 and bestres > prefres and height < bestres )
) ) then
bestres = height
pick = stream
end
end
end
if not pick then
return nil
end
-- Either the "url" or the "cipher" parameter is present,
-- depending on whether the URL signature is scrambled.
local cipher = string.match( pick, '"cipher":"(.-)"' )
if cipher then
-- Scrambled signature: some assembly required
local url = stream_url( cipher, js_url )
if url then
return url
end
end
-- Unscrambled signature, already included in ready-to-use URL
return string.match( pick, '"url":"(.-)"' )
end
-- Probe function.
function probe()
return ( ( vlc.access == "http" or vlc.access == "https" )
@ -301,6 +355,7 @@ function parse()
js_url = string.gsub( js_url, "^//", vlc.access.."://" )
end
-- Classic parameters
if not fmt then
fmt_list = string.match( line, "\"fmt_list\": *\"(.-)\"" )
if fmt_list then
@ -311,11 +366,24 @@ function parse()
url_map = string.match( line, "\"url_encoded_fmt_stream_map\": *\"(.-)\"" )
if url_map then
vlc.msg.dbg( "Found classic parameters for youtube video stream, parsing..." )
-- FIXME: do this properly
url_map = string.gsub( url_map, "\\u0026", "&" )
path = pick_url( url_map, fmt, js_url )
end
-- New-style parameters
if not path then
local stream_map = string.match( line, '\\"formats\\":%[(.-)%]' )
if stream_map then
vlc.msg.dbg( "Found new-style parameters for youtube video stream, parsing..." )
stream_map = string.gsub( stream_map, '\\(["\\/])', '%1' )
-- FIXME: do this properly
stream_map = string.gsub( stream_map, "\\u0026", "&" )
path = pick_stream( stream_map, js_url )
end
end
if not path then
-- If this is a live stream, the URL map will be empty
-- and we get the URL from this field instead
@ -354,6 +422,7 @@ function parse()
elseif string.match( vlc.path, "/get_video_info%?" ) then -- video info API
local line = vlc.readline() -- data is on one line only
-- Classic parameters
local fmt = get_url_param( vlc.path, "fmt" )
if not fmt then
local fmt_list = string.match( line, "&fmt_list=([^&]*)" )
@ -365,10 +434,23 @@ function parse()
local url_map = string.match( line, "&url_encoded_fmt_stream_map=([^&]*)" )
if url_map then
vlc.msg.dbg( "Found classic parameters for youtube video stream, parsing..." )
url_map = vlc.strings.decode_uri( url_map )
path = pick_url( url_map, fmt )
end
-- New-style parameters
if not path then
local stream_map = string.match( line, '%%22formats%%22%%3A%%5B(.-)%%5D' )
if stream_map then
vlc.msg.dbg( "Found new-style parameters for youtube video stream, parsing..." )
stream_map = vlc.strings.decode_uri( stream_map )
-- FIXME: do this properly
stream_map = string.gsub( stream_map, "\\u0026", "&" )
path = pick_stream( stream_map )
end
end
if not path then
-- If this is a live stream, the URL map will be empty
-- and we get the URL from this field instead

Loading…
Cancel
Save