Skip to content

Instantly share code, notes, and snippets.

@Daniel-Mendes
Last active May 8, 2024 13:27
Show Gist options
  • Save Daniel-Mendes/21cff1d94b584769fa05a4905bd272b5 to your computer and use it in GitHub Desktop.
Save Daniel-Mendes/21cff1d94b584769fa05a4905bd272b5 to your computer and use it in GitHub Desktop.
VLC: Youtube Playlist Parser
--[[
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
@kqvanity
Copy link

kqvanity commented Sep 7, 2022

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.

@Daniel-Mendes
Copy link
Author

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

@kqvanity
Copy link

kqvanity commented Sep 7, 2022

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.

@Jolubeat
Copy link

Jolubeat commented Jan 4, 2023

It's working!
Thanks!

@LuisFilipeLP
Copy link

After so many hours of downloading and testing old codes from 2016, I finally found one that adds YouTube playlists to VLC, thanks.

@LuisFilipeLP
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment