-
-
Save Daniel-Mendes/21cff1d94b584769fa05a4905bd272b5 to your computer and use it in GitHub Desktop.
--[[ | |
Youtube playlist parser for VLC media player v1.0.2 | |
Copyright © 2021 Daniel Mendes | |
Author: Daniel Mendes | |
Contact: contact@daniel-mendes.ch | |
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. | |
--]] | |
-- 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 | |
-- Helper emulating vlc.readline() to work around its failure on | |
-- very long lines (see #24957) | |
function read_long_line() | |
local eol | |
local pos = 0 | |
local len = 32768 | |
repeat | |
len = len * 2 | |
local line = vlc.peek( len ) | |
if not line then return nil end | |
eol = string.find( line, "\n", pos + 1 ) | |
pos = len | |
until eol or len >= 1024 * 1024 -- No EOF detection, loop until limit | |
return vlc.read( eol or len ) | |
end | |
-- Unescape Hexadecimal representation | |
function unescape (s) | |
s = string.gsub(s or '', "\\x(%x%x)", function (h) return string.char(tonumber(h,16)) end) | |
return s | |
end | |
-- convert a duration to seconds | |
function durationInSeconds(duration) | |
local hour, min, sec = string.match(duration, "(%d?%d?):?(%d?%d):(%d%d)") | |
if hour == "" then | |
hour = 0 | |
end | |
if min == "" then | |
min = 0 | |
end | |
if sec == "" then | |
sec = 0 | |
end | |
return hour*60*60 + min*60 + sec | |
end | |
-- Probe function. | |
function probe() | |
if vlc.access == "http" and vlc.access == "https" then | |
return false | |
end | |
return ( ( vlc.access == "http" or vlc.access == "https" ) and ( | |
(( | |
string.match( vlc.path, "^www%.youtube%.com/" ) | |
or string.match( vlc.path, "^music%.youtube%.com/" ) -- out of use | |
) and ( | |
string.match(vlc.path, "[?&]list=") -- video page with playlist | |
or string.match( vlc.path, "/playlist%?" ) -- playlist page | |
)) or | |
string.match( vlc.path, "^consent%.youtube%.com/" ) | |
) ) | |
end | |
-- Parse function. | |
function parse() | |
if string.match( vlc.path, "^consent%.youtube%.com/" ) then | |
vlc.msg.info("type: consent.youtube.com") | |
-- Cookie consent redirection | |
-- Location: https://consent.youtube.com/m?continue=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DXXXXXXXXXXX&gl=FR&m=0&pc=yt&uxe=23983172&hl=fr&src=1 | |
-- Set-Cookie: CONSENT=PENDING+355; expires=Fri, 01-Jan-2038 00:00:00 GMT; path=/; domain=.youtube.com | |
local url = get_url_param( vlc.path, "continue" ) | |
if not url then | |
vlc.msg.err( "Couldn't handle YouTube cookie consent redirection, please check for updates to this script or try disabling HTTP cookie forwarding" ) | |
return { } | |
end | |
return { { path = vlc.strings.decode_uri( url ), options = { ":no-http-forward-cookies" } } } | |
elseif string.match( vlc.path, "^www.youtube.com/playlist%?list=" ) then | |
vlc.msg.info("type: youtube.com/playlist?list=") | |
local playlist = {} | |
local item, lines, line | |
local index = 1 | |
local playlistID = get_url_param( vlc.path, "list" ) | |
while true do | |
lines = "" | |
line = "" | |
while line do | |
lines = lines..line | |
line = read_long_line() | |
if not line then break end | |
end | |
if string.match(lines, 'var ytInitialData = ') then | |
local index_start, index_end, group1, group2, group3 = string.find(lines, '(var ytInitialData = )(.*)(;</script><link rel=)') | |
local json = require('dkjson') | |
local jsonParsed = json.decode (group2) | |
for key, video in ipairs(jsonParsed.contents.twoColumnBrowseResultsRenderer.tabs[1].tabRenderer.content.sectionListRenderer.contents[1].itemSectionRenderer.contents[1].playlistVideoListRenderer.contents) do | |
item = nil | |
item = {} | |
-- If continuation playlist | |
if video.continuationItemRenderer then | |
else | |
item.path = "https://www.youtube.com/watch?v="..video.playlistVideoRenderer.videoId | |
item.title = video.playlistVideoRenderer.title.runs[1].text | |
item.duration = video.playlistVideoRenderer.lengthSeconds | |
item.author = video.playlistVideoRenderer.shortBylineText.runs[1].text | |
item.arturl = video.playlistVideoRenderer.thumbnail.thumbnails[3].url | |
table.insert (playlist, item) | |
end | |
end | |
return playlist | |
end | |
end | |
elseif string.match(vlc.path, "(www.youtube.com/watch?).*([?&]list=)") then | |
vlc.msg.info("type: youtube.com/watch?list=") | |
local playlist = {} | |
local item, lines, line | |
local index = 1 | |
local playlistID = get_url_param( vlc.path, "list" ) | |
local videoID = get_url_param( vlc.path, "v" ) | |
while true do | |
lines = "" | |
line = "" | |
while line do | |
lines = lines..line | |
line = read_long_line() | |
if not line then break end | |
end | |
if string.match(lines, 'var ytInitialData = ') then | |
local index_start, index_end, group1, group2, group3 = string.find(lines, '(var ytInitialData = )(.*)(;</script><script nonce=")') | |
local json = require('dkjson') | |
local jsonParsed = json.decode (group2) | |
for key, video in ipairs(jsonParsed.contents.twoColumnWatchNextResults.playlist.playlist.contents) do | |
item = nil | |
item = {} | |
item.path = "https://www.youtube.com/watch?v="..video.playlistPanelVideoRenderer.videoId | |
item.title = video.playlistPanelVideoRenderer.title.simpleText | |
item.duration = durationInSeconds(video.playlistPanelVideoRenderer.lengthText.simpleText) | |
item.author = video.playlistPanelVideoRenderer.shortBylineText.runs[1].text | |
item.arturl = video.playlistPanelVideoRenderer.thumbnail.thumbnails[4].url | |
table.insert (playlist, item) | |
end | |
return playlist | |
end | |
end | |
elseif string.match( vlc.path, "^music%.youtube%.com/playlist%?list=" ) then | |
vlc.msg.info("type: music.youtube.com/playlist?list=") | |
elseif string.match(vlc.path, "(music.youtube.com/watch?).*([?&]list=)") then | |
vlc.msg.info("type: music.youtube.com/watch?list=") | |
end | |
end |
Looks good! Haven't tested but since you only load once, you only support up to 200 videos correct?
If you ever want to load more, I got some javascript code that parses the continuationToken from the page to successively load full playlists here:
https://github.com/Seneral/FlagPlayer/blob/951df5917c1e3c50af96fcaccf12d684d4ed60bf/page/page.js#L1889
A lot of the actual code is abstracted in here since that same continuation logic is used for a bunch of other stuff on the youtube page: https://github.com/Seneral/FlagPlayer/blob/951df5917c1e3c50af96fcaccf12d684d4ed60bf/page/page.js#L1825
Hello Seneral , can I ask you to compile this and fork this up please
or make a revision(if possible) or post it in your gist
Sorry I'm not using this myself, and I really don't have the time for it rn
Looks good! Haven't tested but since you only load once, you only support up to 200 videos correct?
If you ever want to load more, I got some javascript code that parses the continuationToken from the page to successively load full playlists here:
https://github.com/Seneral/FlagPlayer/blob/951df5917c1e3c50af96fcaccf12d684d4ed60bf/page/page.js#L1889
A lot of the actual code is abstracted in here since that same continuation logic is used for a bunch of other stuff on the youtube page: https://github.com/Seneral/FlagPlayer/blob/951df5917c1e3c50af96fcaccf12d684d4ed60bf/page/page.js#L1825
Firstly, thanks for your feedback.
That's true, I tried and my code only loads the 100 first videos.
I tried to implement the query to load the next videos, but with vlc.stream()
you can only pass an url in params and can't pass a request body with the continuationToken
and clickTrackingParams
, so I don't know how I can do it ? (do you have an idea ?)
Sorry I'm not using this myself, and I really don't have the time for it rn
No Problem Brother you might be busy in studying... Stay Strong..
Firstly, thanks for your feedback.
That's true, I tried and my code only loads the 100 first videos.
I tried to implement the query to load the next videos, but with
vlc.stream()
you can only pass an url in params and can't pass a request body with thecontinuationToken
andclickTrackingParams
, so I don't know how I can do it ? (do you have an idea ?)
Ahh I do remember having problems with the VLC Api before...
Previous discussion: https://gist.github.com/Seneral/bd64281ae30a6010e97d956ecd63238e#gistcomment-3631488
Apparently POST is also not supported. So no way to use the YouTube API directly from within VLC, the new unified browse API is completely based on POST requests with data in the body. So unless you recompile VLC or they add more control to the web requests interface, a proper YouTube plugin without external programs is impossible.
That's why the other version had to use youtube-dl
I tried using &index=200
in the url to load the next videos.
But the vlc.stream()
don't seems to work and get the source code of the page, so I don't know.
Code inside function extractAllPlaylistVideos(url)
--[[
Youtube playlist parser for VLC media player v1.0.2
Copyright © 2021 Daniel Mendes
Author: Daniel Mendes
Contact: contact@daniel-mendes.ch
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.
--]]
-- 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
-- Helper emulating vlc.readline() to work around its failure on
-- very long lines (see #24957)
function read_long_line()
local eol
local pos = 0
local len = 32768
repeat
len = len * 2
local line = vlc.peek( len )
if not line then return nil end
eol = string.find( line, "\n", pos + 1 )
pos = len
until eol or len >= 1024 * 1024 -- No EOF detection, loop until limit
return vlc.read( eol or len )
end
-- Unescape Hexadecimal representation
function unescape (s)
s = string.gsub(s or '', "\\x(%x%x)", function (h) return string.char(tonumber(h,16)) end)
return s
end
-- convert a duration to seconds
function durationToSeconds(duration)
local min, sec = string.match(duration, "(%d+):(%d+)")
return min*60 + sec
end
-- extract all the playlist videos
function extractAllPlaylistVideos(url)
local playlist = {}
local item, lines, line, totalVideos, playlistSize
local videoID, playlistID, index
while true do
lines = ""
line = ""
local s = nil
playlistID = get_url_param( url, "list" )
videoID = get_url_param( url, "v" )
index = get_url_param( url, "index" )
s = vlc.stream('http://'..url)
while line do
lines = lines..line
line = s:readline()
if not line then break end
end
vlc.msg.info("THE LINES:"..lines)
if string.match(lines, 'var ytInitialData = ') then
local index_start, index_end, group1, group2, group3 = string.find(lines, '(var ytInitialData = )(.*)(;</script><script nonce=")')
local json = require('dkjson')
local inspect = require('inspect')
local jsonParsed = json.decode (group2)
if(tonumber(index) == 1) then
totalVideos = tonumber(jsonParsed.contents.twoColumnWatchNextResults.playlist.playlist.totalVideos)
end
for key, video in ipairs(jsonParsed.contents.twoColumnWatchNextResults.playlist.playlist.contents) do
item = nil
item = {}
if (video.playlistPanelVideoRenderer.unplayableText) then
totalVideos = totalVideos - 1
else
item.path = "https://www.youtube.com/watch?v="..video.playlistPanelVideoRenderer.videoId
item.title = video.playlistPanelVideoRenderer.title.simpleText
item.duration = durationToSeconds(video.playlistPanelVideoRenderer.lengthText.simpleText)
item.author = video.playlistPanelVideoRenderer.shortBylineText.runs[1].text
item.arturl = vlc.strings.resolve_xml_special_chars(video.playlistPanelVideoRenderer.thumbnail.thumbnails[4].url)
item.meta = {}
item.meta.videoID = video.playlistPanelVideoRenderer.navigationEndpoint.watchEndpoint.videoId
item.meta.index = video.playlistPanelVideoRenderer.navigationEndpoint.watchEndpoint.index + 1
playlist[item.meta.index] = item
end
end
vlc.msg.info("I TRY:"..inspect(playlist))
playlistSize = 0
for _ in pairs(playlist) do playlistSize = playlistSize + 1 end
vlc.msg.info("playlist:"..playlistSize)
vlc.msg.info("totalVideos:"..totalVideos)
if (playlistSize == totalVideos) then
return playlist
end
-- load continuation
url = 'www.youtube.com/watch?v='..playlist[#playlist].meta.videoID..'&list='..playlistID..'&index='..playlist[#playlist].meta.index
end
end
end
-- Probe function.
function probe()
if vlc.access == "http" and vlc.access == "https" then
return false
end
return ( ( vlc.access == "http" or vlc.access == "https" ) and (
((
string.match( vlc.path, "^www%.youtube%.com/" )
or string.match( vlc.path, "^music%.youtube%.com/" ) -- out of use
) and (
string.match(vlc.path, "[?&]list=") -- video page with playlist
or string.match( vlc.path, "/playlist%?" ) -- playlist page
)) or
string.match( vlc.path, "^consent%.youtube%.com/" )
) )
end
-- Parse function.
function parse()
if string.match( vlc.path, "^consent%.youtube%.com/" ) then
vlc.msg.info("type consent.youtube.com")
-- Cookie consent redirection
-- Location: https://consent.youtube.com/m?continue=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DXXXXXXXXXXX&gl=FR&m=0&pc=yt&uxe=23983172&hl=fr&src=1
-- Set-Cookie: CONSENT=PENDING+355; expires=Fri, 01-Jan-2038 00:00:00 GMT; path=/; domain=.youtube.com
local url = get_url_param( vlc.path, "continue" )
if not url then
vlc.msg.err( "Couldn't handle YouTube cookie consent redirection, please check for updates to this script or try disabling HTTP cookie forwarding" )
return { }
end
return { { path = vlc.strings.decode_uri( url ), options = { ":no-http-forward-cookies" } } }
elseif string.match( vlc.path, "^www.youtube.com/playlist%?list=" ) then
vlc.msg.info("type: youtube.com/playlist?list=")
local playlist = {}
local item, lines, line
local index = 1
local playlistID = get_url_param( vlc.path, "list" )
while true do
lines = ""
line = ""
while line do
lines = lines..line
line = read_long_line()
if not line then break end
end
if string.match(lines, 'var ytInitialData = ') then
local index_start, index_end, group1, group2, group3 = string.find(lines, '(var ytInitialData = )(.*)(;</script><link rel=)')
local inspect = require('inspect')
local json = require('dkjson')
local jsonParsed = json.decode (group2)
for key, video in ipairs(jsonParsed.contents.twoColumnBrowseResultsRenderer.tabs[1].tabRenderer.content.sectionListRenderer.contents[1].itemSectionRenderer.contents[1].playlistVideoListRenderer.contents) do
item = nil
item = {}
-- If continuation playlist
if video.continuationItemRenderer then
else
item.path = "https://www.youtube.com/watch?v="..video.playlistVideoRenderer.videoId
item.title = video.playlistVideoRenderer.title.runs[1].text
item.duration = video.playlistVideoRenderer.lengthSeconds
item.author = video.playlistVideoRenderer.shortBylineText.runs[1].text
item.arturl = video.playlistVideoRenderer.thumbnail.thumbnails[3].url
table.insert (playlist, item)
end
end
return playlist
end
end
elseif string.match(vlc.path, "(www.youtube.com/watch?).*([?&]list=)") then
vlc.msg.info("type: youtube.com/watch?v&list=")
local videoID, playlistID, index, url
if(get_url_param(vlc.path, 'index') == '1') then
return extractAllPlaylistVideos(vlc.path)
end
return extractAllPlaylistVideos(vlc.path)
elseif string.match( vlc.path, "^music%.youtube%.com/playlist%?list=" ) then
vlc.msg.info("type: music.youtube.com/playlist?list=")
elseif string.match(vlc.path, "(music.youtube.com/watch?).*([?&]list=)") then
vlc.msg.info("type: music.youtube.com/watch?list=")
end
end
So is there a version for Playlists over 100/200 videos?
So is there a version for Playlists over 100/200 videos?
I did not find how to do it, sorry
Thanks! Your script was the one that worked for me after hassling with other generic plain yt-vlc integration scripts, as it started to raise the lua stream error: Couldn't extract youtube video URL, please check for updates to this script
error message. However, resolution seems to be statically fixed at lower settings. Presumably at 240/144.
Thanks! Your script was the one that worked for me after hassling with other generic plain yt-vlc integration scripts, as it started to raise the
lua stream error: Couldn't extract youtube video URL, please check for updates to this script
error message. However, resolution seems to be statically fixed at lower settings. Presumably at 240/144.
Hello, this message doesn't come from my script but from the vlc youtube.lua
this message doesn't come from my script but from the vlc youtube.lua
That's exactly what i'm saying. Maybe by sentence structure caused a misinterpretation for you. Still, The resolution part, is of this script tho.
It's working!
Thanks!
After so many hours of downloading and testing old codes from 2016, I finally found one that adds YouTube playlists to VLC, thanks.
From what I can see, this code can't identify Youtube Music links, if anyone is wanting this, I came up with a silly "trick" that worked for me, just remove the "music." from the beginning of the link.
Looks good! Haven't tested but since you only load once, you only support up to 200 videos correct?
If you ever want to load more, I got some javascript code that parses the continuationToken from the page to successively load full playlists here:
https://github.com/Seneral/FlagPlayer/blob/951df5917c1e3c50af96fcaccf12d684d4ed60bf/page/page.js#L1889
A lot of the actual code is abstracted in here since that same continuation logic is used for a bunch of other stuff on the youtube page: https://github.com/Seneral/FlagPlayer/blob/951df5917c1e3c50af96fcaccf12d684d4ed60bf/page/page.js#L1825