If you're looking for a fix, see fork https://gist.github.com/p3g4asus/597050997e01f8fd1fcf473fe6545a4f
It works around this by using youtube-dl, since the lua http interface for VLC is not expressive enough to create a proper fix.
If you're looking for a fix, see fork https://gist.github.com/p3g4asus/597050997e01f8fd1fcf473fe6545a4f
It works around this by using youtube-dl, since the lua http interface for VLC is not expressive enough to create a proper fix.
--[[ | |
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 |
Yeah working on it as you can see. Unfortunately they cut out both list_ajax and browse_ajax at the same time (after they previously ditched search_ajax) so I'll have to completely rewrite my code (for another project). Then somebody with more knowledge than me in Lua can port it. I personally won't, in the first place I only did a slight edit to the original script for a one time use case.
youtube_dl is still working though, so tthey apparently prepared for this.
I think I can port it. Is there a way I can learn more about browse interface? Any open source code that is using it or any sort of documentation?
So I've finished updating FlagPlayer, it now uses the browse method for all loaded content (search, lists, related videos, etc) except for comments, where the ajax API is still used.
So if you want, take a look at yt_loadPlaylistData if you want to port it.
Essentially, yt_browse loads the webpage just like a browser does - important parts is to extract the innertube API key and some other 'secret' data and the cookies it sends, as well as the initialData JSON object stored in the webpage which contains not only the first batch of videos in the list but also data on the continuation.
From then on, yt_generateContinuationLoader generates a function (just an abstraction since it's used a lot, don't mind it) that loads the continuation and updates the state with new contination data. That usually consists of a conToken and itctToken. The request ist done by sending a POST request to a fixed URL with the innertube API key in the URL. The headers contain some other secret-not-so-secret data from the page and cookies that have been sent by youtube previously. The body contains the actual continuation info, including itctToken, conToken, and some fake webbrowser identification.
The response should be a JSON object formatted similarily to initialData, details in the code, with an updated conToken and itctToken.
Also note that in contrast to the ajax API, this method keeps deleted/private videos in, so you'll have to filter them out.
Some implementation-specific info for FlagPlayer: It supports mobile as well as desktop, so at some places when parsing, you'll find it takes multiple options - don't mind that too much. Also, since it's a website and youtube is cross-origin, it can't actually access the cookies itself, instead a custom CORS server does the cookie handling, that's why the headers x-cookies/x-set-cookies are used. I expect with the lua API you'll have direct access to the cookies, so don't mind that either.
Hope that helps you get on the right path.
I think I can port it. Is there a way I can learn more about browse interface? Any open source code that is using it or any sort of documentation?
So I've finished updating FlagPlayer, it now uses the browse method for all loaded content (search, lists, related videos, etc) except for comments, where the ajax API is still used.
So if you want, take a look at yt_loadPlaylistData if you want to port it.Essentially, yt_browse loads the webpage just like a browser does - important parts is to extract the innertube API key and some other 'secret' data and the cookies it sends, as well as the initialData JSON object stored in the webpage which contains not only the first batch of videos in the list but also data on the continuation.
From then on, yt_generateContinuationLoader generates a function (just an abstraction since it's used a lot, don't mind it) that loads the continuation and updates the state with new contination data. That usually consists of a conToken and itctToken. The request ist done by sending a POST request to a fixed URL with the innertube API key in the URL. The headers contain some other secret-not-so-secret data from the page and cookies that have been sent by youtube previously. The body contains the actual continuation info, including itctToken, conToken, and some fake webbrowser identification.
The response should be a JSON object formatted similarily to initialData, details in the code, with an updated conToken and itctToken.Also note that in contrast to the ajax API, this method keeps deleted/private videos in, so you'll have to filter them out.
Some implementation-specific info for FlagPlayer: It supports mobile as well as desktop, so at some places when parsing, you'll find it takes multiple options - don't mind that too much. Also, since it's a website and youtube is cross-origin, it can't actually access the cookies itself, instead a custom CORS server does the cookie handling, that's why the headers x-cookies/x-set-cookies are used. I expect with the lua API you'll have direct access to the cookies, so don't mind that either.
Hope that helps you get on the right path.
Thank you for your instructions. Is re-sending Youtube-received cookies mandatory? That would be a very nasty problem to solve in vlc lua interface. Vlc lua api does not support specifying cookies and vlc lua plugin is compiled with LUA_DL_DLL
NOT defined and so it does not allow using lua standard socket.http
dll lib. Honestly I don't know how to solve this issue without re-compiling vlc.
@Seneral thanks for sharing. I've noticed the OG parser extracted the Google Video link (https://xxx.googlevideo.com/videoplayback) instead of the YouTube url (https://www.youtube.com/watch?v=xxxxxxxxxxx). I'm curious if there a way to only play the YouTube url? The Google Video links expire after about 6 hours of starting a playlist.
Thank you for your instructions. Is re-sending Youtube-received cookies mandatory? That would be a very nasty problem to solve in vlc lua interface. Vlc lua api does not support specifying cookies and vlc lua plugin is compiled with
LUA_DL_DLL
NOT defined and so it does not allow using lua standardsocket.http
dll lib. Honestly I don't know how to solve this issue without re-compiling vlc.
Huh, I checked again and it's not necessary. I just kept sending them because previous APIs needed them (not sure which exactly - EDIT: comment aajx API needed them).
So yeah, should work without! Sorry for the confusion.
@Seneral thanks for sharing. I've noticed the OG parser extracted the Google Video link (https://xxx.googlevideo.com/videoplayback) instead of the YouTube url (https://www.youtube.com/watch?v=xxxxxxxxxxx). I'm curious if there a way to only play the YouTube url? The Google Video links expire after about 6 hours of starting a playlist.
This one also only extracts the youtube link, not the raw file. AFAIK VLC can already parse these and extract the video on demand, doesn't it?
@Seneral thanks for sharing. I've noticed the OG parser extracted the Google Video link (https://xxx.googlevideo.com/videoplayback) instead of the YouTube url (https://www.youtube.com/watch?v=xxxxxxxxxxx). I'm curious if there a way to only play the YouTube url? The Google Video links expire after about 6 hours of starting a playlist.
This one also only extracts the youtube link, not the raw file. AFAIK VLC can already parse these and extract the video on demand, doesn't it?
Yes. I can confirm it already does.
Thank you for your instructions. Is re-sending Youtube-received cookies mandatory? That would be a very nasty problem to solve in vlc lua interface. Vlc lua api does not support specifying cookies and vlc lua plugin is compiled with
LUA_DL_DLL
NOT defined and so it does not allow using lua standardsocket.http
dll lib. Honestly I don't know how to solve this issue without re-compiling vlc.Huh, I checked again and it's not necessary. I just kept sending them because previous APIs needed them (not sure which exactly - EDIT: comment aajx API needed them).
So yeah, should work without! Sorry for the confusion.
Thank you for testing. Another problem is the POST request. VLC lua api does not support it. Would GET work anyway? If I click show more link in the youtube playlist page for mobile, a GET is sent (for example: https://m.youtube.com/playlist?ctoken=4qmFsgI8EiRWTFBMOEJ3aXJhTVFMVktlZk02WVl3WHd1YTVnV2tvbVhIcS0aFENBRjZCbEJVT2tOQ1VRJTNEJTNE&pbj=1
) so I assume GET should work anyway. Do you think it can work?
Thank you for your instructions. Is re-sending Youtube-received cookies mandatory? That would be a very nasty problem to solve in vlc lua interface. Vlc lua api does not support specifying cookies and vlc lua plugin is compiled with
LUA_DL_DLL
NOT defined and so it does not allow using lua standardsocket.http
dll lib. Honestly I don't know how to solve this issue without re-compiling vlc.Huh, I checked again and it's not necessary. I just kept sending them because previous APIs needed them (not sure which exactly - EDIT: comment aajx API needed them).
So yeah, should work without! Sorry for the confusion.Thank you for testing. Another problem is the POST request. VLC lua api does not support it. Would GET work anyway? If I click show more link in the youtube playlist page for mobile, a GET is sent (for example:
https://m.youtube.com/playlist?ctoken=4qmFsgI8EiRWTFBMOEJ3aXJhTVFMVktlZk02WVl3WHd1YTVnV2tvbVhIcS0aFENBRjZCbEJVT2tOQ1VRJTNEJTNE&pbj=1
) so I assume GET should work anyway. Do you think it can work?
Oh nice find. This API is apparently still open for both the desktop and mobile domain. Quick tests show it'll work just fine, even without cookies. You do need custom headers though with stuff extractred from the page (take a look at yt_getRequestHeadersYoutube without the cookie parameter).
Then, in javascript terms, the following returns the correct result you can parse:
fetch("https://youtube.com/playlist?ctoken=" + conToken + "&pbj=1", {
method: "GET",
headers: yt_getRequestHeadersYoutube("application/x-www-form-urlencoded", false)
});
Pretty easy. Although I wouldn't trust on it working forever, since they seem to be moving towards a different interface now (same but with POST, with a generic key in the query and the conToken in the body).
Thanks, keep us posted if someone updates the .lua .. I still can't get it to work in VLC
Thank you for your instructions. Is re-sending Youtube-received cookies mandatory? That would be a very nasty problem to solve in vlc lua interface. Vlc lua api does not support specifying cookies and vlc lua plugin is compiled with
LUA_DL_DLL
NOT defined and so it does not allow using lua standardsocket.http
dll lib. Honestly I don't know how to solve this issue without re-compiling vlc.Huh, I checked again and it's not necessary. I just kept sending them because previous APIs needed them (not sure which exactly - EDIT: comment aajx API needed them).
So yeah, should work without! Sorry for the confusion.
Thank you for testing. Another problem is the POST request. VLC lua api does not support it. Would GET work anyway? If I click show more link in the youtube playlist page for mobile, a GET is sent (for example: https://m.youtube.com/playlist?ctoken=4qmFsgI8EiRWTFBMOEJ3aXJhTVFMVktlZk02WVl3WHd1YTVnV2tvbVhIcS0aFENBRjZCbEJVT2tOQ1VRJTNEJTNE&pbj=1
) so I assume GET should work anyway. Do you think it can work?
Thank you for your instructions. Is re-sending Youtube-received cookies mandatory? That would be a very nasty problem to solve in vlc lua interface. Vlc lua api does not support specifying cookies and vlc lua plugin is compiled with
LUA_DL_DLL
NOT defined and so it does not allow using lua standardsocket.http
dll lib. Honestly I don't know how to solve this issue without re-compiling vlc.Huh, I checked again and it's not necessary. I just kept sending them because previous APIs needed them (not sure which exactly - EDIT: comment aajx API needed them).
So yeah, should work without! Sorry for the confusion.Thank you for testing. Another problem is the POST request. VLC lua api does not support it. Would GET work anyway? If I click show more link in the youtube playlist page for mobile, a GET is sent (for example:
https://m.youtube.com/playlist?ctoken=4qmFsgI8EiRWTFBMOEJ3aXJhTVFMVktlZk02WVl3WHd1YTVnV2tvbVhIcS0aFENBRjZCbEJVT2tOQ1VRJTNEJTNE&pbj=1
) so I assume GET should work anyway. Do you think it can work?Oh nice find. This API is apparently still open for both the desktop and mobile domain. Quick tests show it'll work just fine, even without cookies. You do need custom headers though with stuff extractred from the page (take a look at yt_getRequestHeadersYoutube without the cookie parameter).
Then, in javascript terms, the following returns the correct result you can parse:fetch("https://youtube.com/playlist?ctoken=" + conToken + "&pbj=1", { method: "GET", headers: yt_getRequestHeadersYoutube("application/x-www-form-urlencoded", false) });Pretty easy. Although I wouldn't trust on it working forever, since they seem to be moving towards a different interface now (same but with POST, with a generic key in the query and the conToken in the body).
Unfortunately even this type of GET is impossible to reproduce with vlc lua API. I just realized that it supports only customizing referrer and user agent in http GET requests. And unfortunately additional x-youtube headers are mandatory. GET requests without them do not work. Would it be possible to make a simple GET request to FlagPlayer web interface to obtain youtube playlist details in json format?
Thanks in advance
Oh nice find. This API is apparently still open for both the desktop and mobile domain. Quick tests show it'll work just fine, even without cookies. You do need custom headers though with stuff extractred from the page (take a look at yt_getRequestHeadersYoutube without the cookie parameter).
Then, in javascript terms, the following returns the correct result you can parse:fetch("https://youtube.com/playlist?ctoken=" + conToken + "&pbj=1", { method: "GET", headers: yt_getRequestHeadersYoutube("application/x-www-form-urlencoded", false) });Pretty easy. Although I wouldn't trust on it working forever, since they seem to be moving towards a different interface now (same but with POST, with a generic key in the query and the conToken in the body).
Unfortunately even this type of GET is impossible to reproduce with vlc lua API. I just realized that it supports only customizing referrer and user agent in http GET requests. And unfortunately additional x-youtube headers are mandatory. GET requests without them do not work. Would it be possible to make a simple GET request to FlagPlayer web interface to obtain youtube playlist details in json format?
Thanks in advance
That is quite worrying. I'd recommend creating an issue on VLC to try to fix this, because setting headers should really be in their radar.
Technically you could write a server that does these requests for you, but it'd be a lot of rewriting for the one I'm using and I'd have to open it up to other sources, which is generally a bad idea for puplic cors servers which so many options enabled as I have.
And finally, even if you did, YouTube has IP matching protection enabled on some copyrighted videos - VLC wouldnt be able to play back the URL you get, which means you'd have to stream the audio/video file over that server as well.
While there is really no sensible reason hlto have a separate server running for a host application like VLC. For a website, sure, they have unavoidable restrictions set by the browser, but in this case I'd push towards VLC improving their web request interface.
Oh nice find. This API is apparently still open for both the desktop and mobile domain. Quick tests show it'll work just fine, even without cookies. You do need custom headers though with stuff extractred from the page (take a look at yt_getRequestHeadersYoutube without the cookie parameter).
Then, in javascript terms, the following returns the correct result you can parse:fetch("https://youtube.com/playlist?ctoken=" + conToken + "&pbj=1", { method: "GET", headers: yt_getRequestHeadersYoutube("application/x-www-form-urlencoded", false) });Pretty easy. Although I wouldn't trust on it working forever, since they seem to be moving towards a different interface now (same but with POST, with a generic key in the query and the conToken in the body).
Unfortunately even this type of GET is impossible to reproduce with vlc lua API. I just realized that it supports only customizing referrer and user agent in http GET requests. And unfortunately additional x-youtube headers are mandatory. GET requests without them do not work. Would it be possible to make a simple GET request to FlagPlayer web interface to obtain youtube playlist details in json format?
Thanks in advanceThat is quite worrying. I'd recommend creating an issue on VLC to try to fix this, because setting headers should really be in their radar.
Technically you could write a server that does these requests for you, but it'd be a lot of rewriting for the one I'm using and I'd have to open it up to other sources, which is generally a bad idea for puplic cors servers which so many options enabled as I have.
And finally, even if you did, YouTube has IP matching protection enabled on some copyrighted videos - VLC wouldnt be able to play back the URL you get, which means you'd have to stream the audio/video file over that server as well.
While there is really no sensible reason hlto have a separate server running for a host application like VLC. For a website, sure, they have unavoidable restrictions set by the browser, but in this case I'd push towards VLC improving their web request interface.
You are definitely right. I did not think about the implications my request would have. VLC http interface should definitely be extended to support at least setting headers. Before they add that support, playing youtube palylist will be nearly impossible.
The only way I can think of is using os.execute()
api to execute:
youtube-dl -j --flat-playlist "https://<yourYoutubePlaylist>" > outfile.txt
and parsing outfile.txt
Obviously it will be ugly, slow and it will need youtube-dl.exe to be copied in the same dir as the lua playlist plugin file. But it should work. I will try this solution and I will post it when ready.
Here you can find the updated version of this playlist script.
Disclaimer: this version works only under Windows. It can be easily ported but I have no way to test it on other OS at the moment.
Installation (under Windows): place this file in the lua playlist vlc folder together with JSON.lua and youtube-dl.exe
Looking good, will try it out later and somehow attempt to forward everyone there.
(Btw currently there seem to be two copies of the script in there)
@p3g4asus is it broken OR is it due to some youtube api updates recently?
doesn't seem to work for me though..
Stay Strong
@p3g4asus is it broken OR is it due to some youtube api updates recently?
doesn't seem to work for me though..
Stay Strong
It still does work for most videos. I recommend you keep your youtube-dl executable file updated downloading it periodically from its official website
Did the youtube playlist parser stop working for anyone else? It stopped working as of 2/11/21