|
--[[ |
|
Youtube playlist importer for VLC media player 1.1 and 2.0 |
|
Copyright 2012 Guillaume Le Maout |
|
|
|
Authors: Guillaume Le Maout |
|
Contact: http://addons.videolan.org/messages/?action=newmessage&username=exebetche |
|
|
|
This program is free software; you can redistribute it and/or modify |
|
it under the terms of the GNU General Public License as published by |
|
the Free Software Foundation; either version 2 of the License, or |
|
(at your option) any later version. |
|
|
|
This program is distributed in the hope that it will be useful, |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
GNU General Public License for more details. |
|
|
|
You should have received a copy of the GNU General Public License |
|
along with this program; if not, write to the Free Software |
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. |
|
--]] |
|
|
|
--[[ |
|
MODified by Kai Gillmann, 19.01.2013, kaigillmann@googlemail.com: |
|
VLC HAS already a youtube importer, but not for playlists. IMO this mentioned one is |
|
better than this one, because it opens the video in the best possible video resolution. |
|
So i decided to remove all parts of the code which is not responsible for list handling. |
|
Now this lua script parses the list, as wanted, but for each video opened, the vlc default |
|
Youtube script is used, so the videos will be displayed properly. |
|
--]] |
|
|
|
--[[ |
|
Patched by Aaron Hill (https://github.com/seraku24), 2018-05-16: |
|
The original script was failing in VLC 3.x due to an overzealous probe function. |
|
This patch makes the probe function more restrictive to avoid false positives. |
|
--]] |
|
|
|
-- Helper function to get a parameter's value in a URL |
|
function get_url_param( url, name ) |
|
local _, _, res = string.find( url, "[&?]"..name.."=([^&]*)" ) |
|
return res |
|
end |
|
|
|
-- Probe function. |
|
function probe() |
|
if vlc.access ~= "http" and vlc.access ~= "https" then |
|
return false |
|
end |
|
|
|
return string.match(vlc.path:match("([^/]+)"),"%w+.youtube.com") and ( |
|
not string.match(vlc.path, "list_ajax") and string.match(vlc.path, "[?&]list=")) |
|
end |
|
|
|
-- Parse function. |
|
function parse() |
|
if string.match( vlc.path, "list=" ) then |
|
local playlist_parsed, playlistData, line, s, item |
|
local p = {} |
|
local id_ref = {} |
|
local index = 0 |
|
local playlistID = get_url_param( vlc.path, "list" ) |
|
local videoID = get_url_param( vlc.path, "v" ) |
|
local playlistURL = "https://www.youtube.com/list_ajax?action_get_list=1&style=xml&list="..playlistID |
|
local prevLoaded = 0 |
|
|
|
while true do |
|
playlistData = "" |
|
line = "" |
|
s = nil |
|
s = vlc.stream(playlistURL.."&index="..index) |
|
while line do |
|
playlistData = playlistData..line |
|
line = s:readline() |
|
end |
|
|
|
playlist_parsed = nil |
|
playlist_parsed = parse_xml(playlistData).root.video |
|
if playlist_parsed == nil then |
|
playlist_parsed = {} |
|
end |
|
|
|
for i, video in ipairs(playlist_parsed) do |
|
if not id_ref[video.encrypted_id.CDATA] then |
|
|
|
vlc.msg.dbg(i.." "..video.encrypted_id.CDATA) |
|
id_ref[video.encrypted_id.CDATA] = true |
|
|
|
item = nil |
|
item = {} |
|
|
|
if video.encrypted_id |
|
and video.encrypted_id.CDATA then |
|
item.path = "http://www.youtube.com/watch?v="..video.encrypted_id.CDATA |
|
end |
|
|
|
if video.title |
|
and video.title.CDATA then |
|
item.title = video.title.CDATA |
|
end |
|
|
|
if video.artist |
|
and video.artist.CDATA then |
|
item.artist = video.artist.CDATA |
|
end |
|
|
|
if video.thumbnail |
|
and video.thumbnail.CDATA then |
|
item.arturl = video.thumbnail.CDATA |
|
end |
|
|
|
if video.description |
|
and video.description.CDATA then |
|
item.description = video.description.CDATA |
|
end |
|
|
|
--~ item.rating = video.rating |
|
table.insert (p, item) |
|
|
|
end |
|
end |
|
if #p > prevLoaded or index == 100 then |
|
vlc.msg.dbg("Playlist-Youtube: Loaded " ..#p.. " videos...") |
|
index = index + 100 |
|
prevLoaded = #p |
|
else |
|
vlc.msg.dbg("Playlist-Youtube: Finished loading " ..#p.. " videos!") |
|
return p |
|
end |
|
end |
|
end |
|
end |
|
|
|
|
|
function parse_xml(data) |
|
local tree = {} |
|
local stack = {} |
|
local tmp = {} |
|
local tmpTag = "" |
|
local level = 0 |
|
|
|
table.insert(stack, tree) |
|
|
|
for op, tag, attr, empty, val in string.gmatch( |
|
data, |
|
"<(%p?)([^%s>/]+)([^>]-)(%/?)>[%s\r\n\t]*([^<]*)[%s\r\n\t]*") do |
|
if op=="?" then |
|
--~ DOCTYPE |
|
elseif op=="/" then |
|
if level>0 then |
|
level = level - 1 |
|
table.remove(stack) |
|
end |
|
else |
|
level = level + 1 |
|
|
|
if op=="!" then |
|
stack[level]['CDATA'] = vlc.strings.resolve_xml_special_chars( |
|
string.gsub(tag..attr, "%[CDATA%[(.+)%]%]", "%1")) |
|
attr = "" |
|
level = level - 1 |
|
elseif type(stack[level][tag]) == "nil" then |
|
stack[level][tag] = {} |
|
table.insert(stack, stack[level][tag]) |
|
else |
|
if type(stack[level][tag][1]) == "nil" then |
|
tmp = nil |
|
tmp = stack[level][tag] |
|
stack[level][tag] = nil |
|
stack[level][tag] = {} |
|
table.insert(stack[level][tag], tmp) |
|
end |
|
tmp = nil |
|
tmp = {} |
|
table.insert(stack[level][tag], tmp) |
|
table.insert(stack, tmp) |
|
end |
|
|
|
if val~="" then |
|
stack[level][tag]['CDATA'] = {} |
|
stack[level][tag]['CDATA'] = vlc.strings.resolve_xml_special_chars(val) |
|
end |
|
|
|
if attr ~= "" then |
|
stack[level][tag]['ATTR'] = {} |
|
string.gsub(attr, |
|
"(%w+)=([\"'])(.-)%2", |
|
function (name, _, value) |
|
stack[level][tag]['ATTR'][name] = value |
|
end) |
|
end |
|
|
|
if empty ~= "" then |
|
level = level - 1 |
|
table.remove(stack) |
|
end |
|
end |
|
end |
|
return tree |
|
end |
Ok after a quick look, the list_ajax simply ignores the index parameter now, for some user lists only. I do not know if there is a different parameter now, but I really hope there is. Alternative is the browse_ajax way, which the default playlist site uses. I have a version of that implemented for FlagPlayer, I could adapt it, but considering this only affects a few user playlists I probably won't be putting in too much effort into that, at least not for an old lua script I've only used once haha
Basically you'd have to load the playlist page, get the initial data out of the html file, parse the video data in there, and check if there's a "nextContinuationData" with "continuation" and "clickTrackingParams" (aka ctoken and itct). If there is, you need to parse some additional stuff from the page, set some cookies, call browse_ajax with these parameters and some special content (along with the cookies), and rinse and repeat. Again, I have this implemented for browse content, however I have no motivation to convert this javascript code to lua.
If you need want, it's implemented here:
Initial load, most importantly yt_browse.
And continuing the load, yt_loadChannelPageUploads.
Again, it's complex, so I wouldn't count on it.
Edit: to see how it works, and see the network requests, you can load the following page and see that while the playlist only loads 100 videos, if you scroll down it does actually load all videos:
https://flagplayer.seneral.dev/?list=UU-lHJZR3Gqxm24_Vd_AJ5Yw&u=PewDiePie