--[[
Copyright (C) 2011-2013 Anton Burdinuk
clark15b@gmail.comhttps://tsdemuxer.googlecode.com/svn/trunk/xupnpdsysmer add support api v3 (need install curl with ssl)
20150524 AnLeAl changes:
in url fetch, added docs section
20150527 AnLeAl changes:
fixed for video get when less than 50
returned ui config for user amount video
add possibility get more than 50 videos
20150527 MejGun changes:
code refactoring for feed update
20150530 AnLeAl changes:
small code cleanup
added 'channel/mostpopular' for youtube mostpopular videos (it's only 30 from api), also region code from ui working
added favorites/username to get favorites
added search function
20150531 AnLeAl changes:
fixed error when only first feed can get all videos for cfg.youtube_video_count and other no more 50
ui help updated
curl settings from cycles was moved to variables
20150612 AnLeAl changes:
added playlist/playlistid option
ui help updated
doc section updated
sysmer changes:
play crypt video (vevo) - add vlc youtube plugin
play video with login youtube
sysmer changes:
new function youtube_updatefeed
20150912 AnLeAl changes
fixed search function for non english words with spaces
fixed search order: date
(-) removed favorites/username to get favorites
(-) restricted amount of video to 50
(-) removed links with "friendly names" of feeds (used in WebUI of Xupnpd )
20151103 mihd changes
added possibility to get more than 50 videos
repaired links with "friendly names" of feeds (used in WebUI of Xupnpd ). If "friendly name" is empty, used fullname of channel(user) from youtube.
added sequence numbers for videos (for receivers, sorting videos only alphabetically)
added favorites/username and favorites/id to get favorites
fixed search function, added regional language. ("search/search_string/optionalregion/optionallanguage", where "optionalregion" from
http://www.iso.org/iso/country_codes/is ... ements.htm and "optionallanguage" (ISO 639-1 Code) from
http://www.loc.gov/standards/iso639-2/php/code_list.php )
20151216 Slider26 changes
added protection of critical error in "youtube_updatefeed" function.
any criticar error in this function (on xupnpd v.1.033) causes abnormal termination of "Update Feed" process.
this can happen, for example, during processing "playlist/id" feed, where id is not exists.
all errors writes in plugins/xupnpd_youtube.log file like: "Date: "Error text" in process of feed "feed" ["friendly_name"]"
]]
--[[
README
This is YouTube api v3 plugin for xupnpd.
Be accurate when search for real username or playlist id.
Quickstart:
1. Place this file into xupnpd plugin directory.
2. Go to google developers console:
https://developers.google.com/youtube/r ... tion?hl=ru3. You need API Key, choose Browser key:
https://developers.google.com/youtube/r ... e_API_Keys4. Don't use option: only allow referrals from domains.
5. Replace '***' with your new key in section '&key=***' in this file. Save file.
6. Restart xupnpd, remove any old feeds that was made for youtube earlier. Add new one based on ui help patterns.
7. Enjoy!
18 - 360p (MP4,h.264/AVC)
22 - 720p (MP4,h.264/AVC) hd
37 - 1080p (MP4,h.264/AVC) hd
82 - 360p (MP4,h.264/AVC) stereo3d
83 - 480p (MP4,h.264/AVC) hq stereo3d
84 - 720p (MP4,h.264/AVC) hd stereo3d
85 - 1080p (MP4,h.264/AVC) hd stereo3d
]]
cfg.youtube_fmt=37
cfg.youtube_region='*'
cfg.youtube_video_count=100
function add_to_error_log(error_string)
local logfile_path=cfg.plugin_path..'xupnpd_youtube.log'
local dfd=io.open(logfile_path,'a')
if dfd then
dfd:write(os.date() .. ': ' .. error_string .. '\n')
dfd:close()
end
end
function youtube_updatefeed(feed,friendly_name)
local res_call
local res_func
res_call, res_func = pcall(youtube_updatefeed_unprotected,feed,friendly_name)
if res_call then
return res_func
else
add_to_error_log('\"' .. res_func .. '\" in process of feed \"' .. feed .. '\" [\"' .. friendly_name .. '\"]')
return false
end
end
function youtube_updatefeed_unprotected(feed,friendly_name)
local function isempty(s)
return s == nil or s == ''
end
local data = nil
local jsondata = nil
local uploads = nil
local tr = nil
local region = ''
local lang = ''
local rc = false
local num = cfg.youtube_video_count
local key = '&key=AIzaSyCDkNYxOUfpyNECVrqhjv23vVVQOgeVHfE' -- change *** to your youtube api key from:
https://console.developers.google.com local c = 'https://www.googleapis.com/youtube/v3/channels?part=contentDetails&forUsername='
local u = 'https://www.googleapis.com/youtube/v3/channels?part=contentDetails&id='
local i = 'https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&playlistId='
local s = 'https://www.googleapis.com/youtube/v3/search?part=snippet&type=video&order=date&q='
local cn = 'https://www.googleapis.com/youtube/v3/channels?part=snippet&id='
local cnu = 'https://www.googleapis.com/youtube/v3/channels?part=snippet&forUsername='
local pl = 'https://www.googleapis.com/youtube/v3/playlists?part=snippet&id='
local mp = 'https://www.googleapis.com/youtube/v3/videos?part=snippet&chart=mostPopular'
local fordata = ''
local pagetokenA = ''
local nextpageA = ''
local enough = false
local forfeedn = ''
local numA = 50 -- show 50 videos per page 0..50 from youtube api v3
local maxA = '&maxResults=' .. numA
if num < numA then
maxA = '&maxResults=' .. num
numA = num
end
if cfg.youtube_region and cfg.youtube_region~='*' then
region='®ionCode='..cfg.youtube_region
forfeedn = cfg.youtube_region
else
region = ''
forfeedn = 'worldwide'
end
feed = string.gsub(feed,'//','/empt/')
if string.sub(feed,1,7) == 'search/' and string.find(feed,'/',8) then
if string.sub(feed,string.find(feed,'/',8),string.find(feed,'/',8)+5) == '/empt/' then
feed = string.gsub(feed,'/empt/','/'..forfeedn..'/',1)
end
end
if (string.sub(feed,1,7) == 'search/' and string.find(feed,'/',8) == nil) or (string.sub(feed,1,8) == 'channel/' and string.find(feed,'/',9) == nil) then
feed = feed..'/'..forfeedn
end
local tfeed = split_string(feed,'/')
local feed_name = formatfeedn(feed)
if table.getn(tfeed) == 1 then
data = curl(c .. tfeed[1].. key)
if string.find(data,'"totalResults": 0,') then
data = curl(u .. tfeed[1].. key)
feed_name = curl(cn .. tfeed[1].. key)
else
feed_name = curl(cnu .. tfeed[1].. key)
end
feed_name = json.decode(feed_name)
feed_name=formatfeedn(feed_name['items'][1]['snippet']['title'])
jsondata = json.decode(data)
uploads = jsondata['items'][1]['contentDetails']['relatedPlaylists']['uploads']
fordata = i .. uploads .. key
end
if table.getn(tfeed) > 1 then
if tfeed[3] and tfeed[3]~='worldwide' then
region = '®ionCode='..string.upper(tfeed[3])
end
if tfeed[4] then
lang = '&relevanceLanguage='..string.lower(tfeed[4])
end
if tfeed[1] == 'search' then
fordata = s .. util.urlencode(tfeed[2]) .. key .. '&videoDefinition=high&videoDimension=2d' .. region .. lang
end
if tfeed[1] == 'playlist' then
fordata = i .. tfeed[2] .. key
feed_name = curl(pl .. tfeed[2].. key)
feed_name = json.decode(feed_name)
feed_name='[PL] ' .. formatfeedn(feed_name['items'][1]['snippet']['title'])
end
if tfeed[1] == 'favorites' then
data = curl(c .. tfeed[2].. key)
if string.find(data,'"totalResults": 0,') then
data = curl(u .. tfeed[2].. key)
feed_name = curl(cn .. tfeed[2].. key)
else
feed_name = curl(cnu .. tfeed[2].. key)
end
feed_name = json.decode(feed_name)
feed_name='[FAV] ' .. formatfeedn(feed_name['items'][1]['snippet']['title'])
jsondata = json.decode(data)
uploads = jsondata['items'][1]['contentDetails']['relatedPlaylists']['likes']
fordata = i .. uploads .. key
end
if tfeed[1] == 'channel' and tfeed[2] == 'mostpopular' then
fordata = mp .. key .. region
end
end
local feed_m3u_path=cfg.feeds_path..feed_name..'.m3u'
local tmp_m3u_path=cfg.feeds_path..feed_name..'.tmp'
local dfd=io.open(tmp_m3u_path,'w+')
if dfd then
if string.find(friendly_name,'youtube ') then
plname = feed_name
else
plname = friendly_name
end
dfd:write('#EXTM3U name=\"',plname,'\" type=mp4 plugin=youtube\n')
local i = 0
while true do
data = curl(fordata .. maxA .. pagetokenA)
if data == nil or string.find(data,'"totalResults": 0,') or string.find(data,'"errors":') then
break
end
jsondata = json.decode(data)
tr = jsondata['pageInfo']['totalResults']
if num > tr then num = tr end
local vid = nil
local title = nil
local url = nil
local img = nil
for key,value in pairs(jsondata['items']) do
i = i + 1
if i > num then
enough = true
break
end
if tfeed[1] == 'channel' and tfeed[2] == 'mostpopular' then
vid = value['id']
elseif tfeed[1] == 'search'then
vid = value['id']['videoId']
else
vid = value['snippet']['resourceId']['videoId']
end
local title = value['snippet']['title']
local url = 'http://www.youtube.com/watch?v=' .. vid
local img = 'http://i.ytimg.com/vi/' .. vid .. '/mqdefault.jpg'
dfd:write('#EXTINF:0 logo=',img,' ,',i,'. ',title,'\n',url,'\n')
end
if isempty(jsondata['nextPageToken']) or enough then
break
else
nextpageA = jsondata['nextPageToken']
pagetokenA = '&pageToken=' .. nextpageA
end
jsondata=nil
end
if util.md5(tmp_m3u_path)~=util.md5(feed_m3u_path) then
if os.execute('mv \"' .. tmp_m3u_path .. '\" \"' .. feed_m3u_path .. '\"')==0 then
rc=true
end
else
util.unlink(tmp_m3u_path)
end
end
dfd:close()
return rc
end
function youtube_sendurl(youtube_url,range)
local url=nil
if plugin_sendurl_from_cache(youtube_url,range) then return end
url=youtube_get_video_url(youtube_url)
if url then
if cfg.debug>0 then print('YouTube Real URL: '..url) end
plugin_sendurl(youtube_url,url,range)
else
if cfg.debug>0 then print('YouTube clip is not found') end
plugin_sendfile('www/corrupted.mp4')
end
end
function youtube_get_video_url(youtube_url)
local url = nil
local js_url = nil
local sig = nil
local sts = nil
local tmp = nil
local ur = {}
local si = {}
local i = 1
local id = split_string(youtube_url,'=')
local info_page = 'http://www.youtube.com/get_video_info?&video_id='..id[2]..'&el=info&ps=default&eurl=&gl=US&hl=en'
local embed_page = 'http://www.youtube.com/embed/'..id[2]
embed_page = plugin_download(embed_page)
embed_page = string.match(embed_page,'PLAYER_CONFIG(.-)%);')
embed_page = '{' .. string.match(embed_page,'{(.*)}') .. '}'
embed_page = json.decode(embed_page)
sts = embed_page.sts
js_url = 'http:' .. embed_page.assets.js
url = plugin_download(info_page .. '&sts=' .. sts)
tmp = string.match(url,'url_encoded_fmt_stream_map=(.-)&')
tmp = string.gsub(tmp,'%%2C','!')
tmp = string.gsub(tmp,'%%26','!')
tmp = split_string(tmp,'!')
for key, val in pairs(tmp) do
if string.find(val,'url%%3D') then
ur[i] = string.match(val,'url%%3D(.*)')
i = i + 1
end
end
i = 1
for key, val in pairs(tmp) do
if string.find(val,'s%%3D') then
si[i] = string.match(val,'s%%3D(.*)')
i = i + 1
end
end
for key, val in pairs(ur) do
if string.find(val,'itag%%253D' .. cfg.youtube_fmt) then
if string.find(val,'signature') then
return util.urldecode(util.urldecode(val))
else
sig = si[key]
sig = js_descramble( sig, js_url )
return util.urldecode(util.urldecode(val)) .. '&signature=' .. sig
end
end
end
if string.find(ur[1],'signature') then
return util.urldecode(util.urldecode(ur[1]))
else
sig = si[1]
sig = js_descramble( sig, js_url )
return util.urldecode(util.urldecode(ur[1])) .. '&signature=' .. sig
end
end
function js_descramble( sig, js_url )
local js = plugin_download( js_url )
local descrambler = nil
descrambler = string.match( js, "%.sig||([a-zA-Z0-9$]+)%(" )
local transformations = nil
local rules = nil
transformations, rules = string.match( js, "var ..={(.-)};function "..descrambler.."%([^)]*%){(.-)}" )
local trans = {}
for meth,code in string.gmatch( transformations, "(..):function%([^)]*%){([^}]*)}" ) do
if string.match( code, "%.reverse%(" ) then
trans[meth] = "reverse"
elseif string.match( code, "%.splice%(") then
trans[meth] = "slice"
elseif string.match( code, "var c=" ) then
trans[meth] = "swap"
end
end
for meth,idx in string.gmatch( rules, "..%.(..)%([^,]+,(%d+)%)" ) do
idx = tonumber( idx )
if trans[meth] == "reverse" then
sig = string.reverse( sig )
elseif trans[meth] == "slice" then
sig = string.sub( sig, idx + 1 )
elseif trans[meth] == "swap" then
if idx > 1 then
sig = string.gsub( sig, "^(.)("..string.rep( ".", idx - 1 )..")(.)(.*)$", "%3%2%1%4" )
elseif idx == 1 then
sig = string.gsub( sig, "^(.)(.)", "%2%1" )
end
end
end
return sig
end
function curl( data )
data = io.popen('curl -k ' .. '"' .. data .. '"')
data = data:read('*all')
return data
end
function formatfeedn( feed_name )
feed_name = string.gsub(feed_name,'[\:*?<>|]','')
feed_name = string.gsub(feed_name,'\"','\'')
if string.sub(feed_name,1,7) == 'search/' then
feed_name = string.gsub(feed_name,'/',' \'',1)
if string.find(feed_name,'/') then
feed_name = string.gsub(feed_name,'/','\' ',1)
else
feed_name = feed_name .. '\''
end
end
if string.sub(feed_name,1,8) == 'channel/' then
feed_name = string.sub(feed_name,9)
end
feed_name = string.gsub(feed_name,'/',' ')
feed_name = string.gsub(feed_name,'-','_')
return feed_name
end
plugins['youtube']={}
plugins.youtube.name="YouTube"
plugins.youtube.desc="<i>name</i> from
http://www.youtube.com/user/<i>name</i> or <i>id</i> from
http://www.youtube.com/channel/<i>id</i>,<br>" ..
"favorites/<i>username</i> or favorites/<i>id</i>,<br>" ..
"search/<i>search_string</i>/optional<i><a href=http://www.iso.org/iso/country_codes/iso_3166_code_lists/country_names_and_code_elements.htm>region</a></i>/optional<i><a href=http://www.loc.gov/standards/iso639-2/php/code_list.php>language(ISO 639-1 Code)</a></i>,<br>" ..
"playlist/<i>id</i>,<br>" ..
"YouTube channels: channel/mostpopular/optional<i><a href=http://www.iso.org/iso/country_codes/iso_3166_code_lists/country_names_and_code_elements.htm>region</a></i>"
plugins.youtube.sendurl=youtube_sendurl
plugins.youtube.updatefeed=youtube_updatefeed
plugins.youtube.getvideourl=youtube_get_video_url
plugins.youtube.ui_config_vars=
{
{ "select", "youtube_fmt", "int" },
{ "select", "youtube_region" },
{ "input", "youtube_video_count", "int" }
}