Playing Youtube playlists

Discuss your Lua playlist, album art and interface scripts.
neowam
New Cone
New Cone
Posts: 1
Joined: 17 Sep 2008 14:56

Playing Youtube playlists

Postby neowam » 17 Sep 2008 14:57

I want to have a .lua file that recognizes a playlist of per example 8 videos and that can be playing within VLC player and also using the skip buttons. Is this possible in 9.2?

dionoea
Cone Master
Cone Master
Posts: 5157
Joined: 03 Dec 2003 23:09
Location: Paris, France

Re: Playing Youtube playlists

Postby dionoea » 17 Sep 2008 15:52

Sure, everything is possible :) Do you have an example page with such playlists? Did you already begin writing the lua parser?
Antoine Cellerier
dionoea
(Please do not use private messages for support questions)

uky
New Cone
New Cone
Posts: 6
Joined: 18 Aug 2009 00:58

Re: Playing Youtube playlists

Postby uky » 18 Aug 2009 01:48

I've searched around a bit for playing YouTube playlists in VLC. This seemed to be the most relevant thread though an unresolved one.

I decided to try to make a Lua script for it myself. It's my first time with Lua so I imagine I'm not doing things as well as I could be.

Here's my first draft of it:

Code: Select all

--[[ Parse YouTube playlists: http://youtube.com/view_play_list?p=<playlist_ID> http://gdata.youtube.com/feeds/api/playlists/<playlist_ID> http://gdata.youtube.com/feeds/api/playlists/<playlist_ID>?v=2 --]] function probe() if vlc.access ~= "http" then return false end return ( string.match( vlc.path, "youtube.com/view_play_list%?" ) or string.match( vlc.path, "gdata.youtube.com/feeds/api/playlists/" ) ) end function parse() -- if webpage URL, redirect to YouTube API v2 XML -- the playlist webpage HTML layout may change over time but a specific version of the YouTube API shouldn't if string.match( vlc.path, "view_play_list%?" ) then playlist_ID = string.match( vlc.path, "[?&]p=([^&]+)" ) xml = {} xml.path = "http://gdata.youtube.com/feeds/api/playlists/" .. playlist_ID .. "?v=2" xml.path = xml.path .. "&max-results=1" -- prevents video URL expiration but now you can't easily browse videos out of order... xml.name = "YouTube playlist" return { xml } end -- parse feed XML and let existing YouTube Lua script handle individual videos -- NOTE: this XML doesn't have line breaks after each tag. the only line breaks are from text content enclosed in tags videos = {} while true do line = vlc.readline() if not line then break end for video_url in string.gmatch( line, "<media:player url='([^']+)'/>" ) do table.insert( videos, { path = vlc.strings.resolve_xml_special_chars( video_url ) } ) end -- check if there's a following XML file with more videos if string.match( line, "<link rel='next' type='application/atom%+xml' href='([^']+)'/>" ) then next_page = string.match( line, "<link rel='next' type='application/atom%+xml' href='([^']+)'/>" ) end end -- process next page of results after current items if there is one if next_page then table.insert( videos, { path = vlc.strings.resolve_xml_special_chars( next_page ); name = "more YouTube playlist videos..." } ) end return videos end
It works okay for my purposes but is less than ideal. Originally I tried just parsing the URLs of each video in the playlist and returning those to let the existing Youtube.lua script to handle everything. That was okay for short playlists but for long ones the later links that the Youtube.lua script generated had expired by the time they were accessed.

Now I page through the videos in the YouTube playlist one at a time. This is easy enough using the YouTube Data API but is inconvenient if you wanted to browse through the videos in the playlist out of order for any reason. It also makes use of a feature in VLC I wasn't sure was intentional.

It seems if there are items in VLC playlist not yet processed by the Lua scripts and playback starts on a video, they stay unprocessed until playback reaches them? If so, that's useful for my purposes since if a YouTube item in VLC's playlist only gets processed at playback then expiration is not really a problem.

What happens seems a little odd though. I add a YouTube playlist URL to the "Open Network Stream" and my script processes that to return the first video and a URL for my script to process to get the second video. The first video gets processed by Youtube.lua and my script processes the second page to return the second video and third page but at this point playback starts. The second video remains unprocessed by Youtube.lua and the third page unprocessed by my script until playback finishes with the current video and moves on. At this point this repeats with the second video playing, third video left unprocessed by Youtube.lua and fourth page unprocessed by my script until the second video finishes. I've watched a few long YouTube playlists with this script now and it does successfully work it's way through all the videos.

All in all, it ends up working correctly but it just seemed a little weird to me. Is this behavior I can depend on?

Any one have any ideas how to have the best of both worlds though? Avoiding video URL expiration while being able to have all the videos in VLC's playlist for easy browsing. Or any other suggestions in general about my code? As I said, it's my first time with Lua so I expect my code may not be so great.

ducati67
New Cone
New Cone
Posts: 1
Joined: 18 Aug 2009 23:43

Re: Playing Youtube playlists

Postby ducati67 » 22 Aug 2009 04:39

WOW great job! Working pretty good for me. Just some feature requests for the next update:
1) Allow it to play the Youtube quicklist also
2) Can this playlist be saved? I had problems saving the playlist and reloading it

uky
New Cone
New Cone
Posts: 6
Joined: 18 Aug 2009 00:58

Re: Playing Youtube playlists

Postby uky » 22 Aug 2009 08:16

WOW great job! Working pretty good for me. Just some feature requests for the next update:
1) Allow it to play the Youtube quicklist also
Quicklists? Hmm... I've never used those so I'm not familiar with them. Is there a public way to see someone's quicklist? I don't see them even mentioned in the YouTube API.
2) Can this playlist be saved? I had problems saving the playlist and reloading it


What do you mean by "saved"? Since it pages through the videos one at a time I've been able to stop a playlist half way through and continue the rest of the videos later that day. If you mean being able to replay an already played item again... if it's been more than a few hours it will have expired. That's why my script only shows you one video at a time... because when Youtube.lua runs on an individual video it replaces that entry in the VLC playlist with a link to a video from that page which has an expiration date. So I try to have a video processed by Youtube.lua at the latest moment possible.

I'm not sure if there's a way to edit that script (the stock Youtube.lua one) to detect if a video has expired and obtain a new video automatically. I guess I could at least look into it. That would solve a few problems...

This doesn't have either of the things you've mentioned yet but here's my latest version of the script. The only thing it adds is some metadata support:

Code: Select all

--[[     Parse YouTube playlists:         http://youtube.com/view_play_list?p=<playlist_ID>         http://gdata.youtube.com/feeds/api/playlists/<playlist_ID>         http://gdata.youtube.com/feeds/api/playlists/<playlist_ID>?v=2         TODO: determine if it's feasible to allow easy video browsing through playlist                while preventing video URL expiration         TODO: add some ability to select different video qualities with &fmt=<quality> --]] function probe()     if vlc.access ~= "http" then         return false     end     return ( string.match( vlc.path, "youtube.com/view_play_list%?" )             or string.match( vlc.path, "gdata.youtube.com/feeds/api/playlists/" ) ) end function parse()     -- if webpage URL, redirect to YouTube API v2 XML     -- the output from the YouTube API is easier to work with and should change less often than the webpage     if string.match( vlc.path, "view_play_list%?" ) then         playlist_ID = string.match( vlc.path, "[?&]p=([^&]+)" )         xml = {}         xml.name = "YouTube playlist"         xml.path = "http://gdata.youtube.com/feeds/api/playlists/" .. playlist_ID .. "?v=2"         -- paging through videos one at a time prevents video URL expiration but now you can't easily browse         -- videos out of order...         xml.path = xml.path .. "&max-results=1"         return { xml }     end     -- get current position in playlist     if string.match( vlc.path, "[?&]start%-index=([^&]+)" ) then         video_position = string.match( vlc.path, "[?&]start%-index=([^&]+)" )     else         video_position = 1     end     -- parse feed XML and let existing YouTube Lua script handle individual videos     -- NOTE: this XML doesn't have line breaks after each tag. the only line breaks are from text content     --       enclosed in tags     videos = {}     while true do         line = vlc.readline()         if not line then break end         -- get playlist name         if not playlist_name then -- first <title> tag is for playlist             if string.match( line, "<title>([^<]+)</title>" ) then                 playlist_name = string.match( line, "<title>([^<]+)</title>" )             end         end         -- check if there's a following XML file with more videos         if string.match( line, "<link rel='next' type='application/atom%+xml' href='([^']+)'/>" ) then             next_page = string.match( line, "<link rel='next' type='application/atom%+xml' href='([^']+)'/>" )             next_page = vlc.strings.resolve_xml_special_chars( next_page ) -- fix the "&"s         end         -- get artist of playlist         if not playlist_artist then -- first <name> tag is for playlist             if string.match( line, "<name>([^<]+)</name>" ) then                 playlist_artist = string.match( line, "<name>([^<]+)</name>" )             end         end         -- get video count in playlist         if string.match( line, "<openSearch:totalResults>(%d+)</openSearch:totalResults>" ) then             video_count = string.match( line, "<openSearch:totalResults>(%d+)</openSearch:totalResults>" )             video_count = tonumber( video_count )         end         -- get playlist description         if string.match( line, "<media:description [^>]*>([^<]+)</media:description>" ) then             playlist_desc = string.match( line, "<media:description [^>]*>([^<]+)</media:description>" )         end         -- get all videos from playlist         -- NOTE: although using the webpage URL defaults there to only being one video, you can still manually         --       enter a different YouTube API playlist feed URL which can have more than one         for video_url in string.gmatch( line, "<media:player url='([^']+)'/>" ) do             video_item = {}             video_item.path = vlc.strings.resolve_xml_special_chars( video_url )             video_item.name = playlist_name .. " (video " .. video_position .. ")"             video_item.artist = playlist_artist             table.insert( videos, video_item )             video_position = video_position + 1         end     end     -- process next page of results after current items if there is one     if next_page then         next_video_position = tonumber( string.match( next_page, "[?&]start%-index=(%d+)" ) )         next_xml = {}         next_xml.path = next_page         if next_video_position < video_count then             next_xml.name = playlist_name .. " (videos " .. next_video_position .. " to " .. video_count .. ")"         else             next_xml.name = playlist_name .. " (video " .. next_video_position .. ")"         end         next_xml.description = playlist_desc         next_xml.artist = playlist_artist         table.insert( videos, next_xml )     end     return videos end
edit: fixed minor bug in script with metadata

uky
New Cone
New Cone
Posts: 6
Joined: 18 Aug 2009 00:58

Re: Playing Youtube playlists

Postby uky » 22 Aug 2009 12:24

The easiest way I could think getting expired videos to work didn't work:

Code: Select all

function probe() return vlc.access == "http" and string.match( vlc.path, "youtube.com/get_video%?" ) end function parse() if string.match( vlc.path, "[?&]video_id=([^&]+)" ) then video_id = string.match( vlc.path, "[?&]video_id=([^&]+)" ) return "http://youtube.com/watch?v=" .. video_id end end
I don't quite fully understand what all happens between a URL being added as a network stream and whether or not it gets processed by Lua scripts. An expired YouTube video URL doesn't seem to get processed by that script above. I would have thought the process would be something along the lines of:
  1. Check if a playable media file is behind the URL
  2. If not, try Lua scripts
but it seems more complicated than that. Watching the debug messages while adding an expired URL shows it failing to find a video (404 error) and then it just stops doing anything. Perhaps it's precisely because it's a 404 error that it's assumed there is no point in doing anything else with it.

So sorry ducati, I'm not really sure about saving the playlist. That ability's not unrelated to some of the stuff I was asking for advice for in my first post. I'd certainly like to be able to do that.

david4utv
New Cone
New Cone
Posts: 5
Joined: 11 Oct 2009 22:19

Re: Playing Youtube playlists

Postby david4utv » 11 Oct 2009 22:24

how can i use (activate) lua from a libvlc object (i am using c#)...

uky
New Cone
New Cone
Posts: 6
Joined: 18 Aug 2009 00:58

Re: Playing Youtube playlists

Postby uky » 11 Oct 2009 22:53

A small update for anyone using my Lua script here. No new features. It just adds support for the playlist URLs you see on the new-style YouTube channels (e.g. youtube.com/user/<user>#(play|grid)/user/<playlist_ID>) since the old-style channels are being phased out. Now that I think about it, I shoud have added support for them long before this. Oops.

Code: Select all

--[[ Parse YouTube playlists: http://youtube.com/view_play_list?p=<playlist_ID> http://youtube.com/user/<user>#<mode>/(user|c)/<playlist_ID> http://gdata.youtube.com/feeds/api/playlists/<playlist_ID>?v=2 TODO: determine if it's feasible to allow easy video browsing through playlist while preventing video URL expiration TODO: add some ability to select different video qualities with &fmt=<quality> --]] function probe() if vlc.access ~= "http" then return false end return ( string.match( vlc.path, "youtube.com/view_play_list%?" ) or string.match( vlc.path, "gdata.youtube.com/feeds/api/playlists/" ) or string.match( vlc.path, "youtube.com/user/[^/]+/[^/]+/%x+" ) ) end function parse() -- if webpage URL, redirect to YouTube API v2 XML -- the output from the YouTube API is easier to work with and should change less often than the webpage if string.match( vlc.path, "view_play_list%?" ) or string.match( vlc.path, "youtube.com/user" ) then if string.match( vlc.path, "view_play_list%?" ) then playlist_ID = string.match( vlc.path, "[?&]p=([^&]+)" ) else playlist_ID = string.match( vlc.path, "youtube.com/user/[^/]+/[^/]+/(%x+)" ) end xml = {} xml.name = "YouTube playlist" xml.path = "http://gdata.youtube.com/feeds/api/playlists/" .. playlist_ID .. "?v=2" -- paging through videos one at a time prevents video URL expiration but now you can't easily browse -- videos out of order... xml.path = xml.path .. "&max-results=1" return { xml } end -- get current position in playlist if string.match( vlc.path, "[?&]start%-index=([^&]+)" ) then video_position = string.match( vlc.path, "[?&]start%-index=([^&]+)" ) else video_position = 1 end -- parse feed XML and let existing YouTube Lua script handle individual videos -- NOTE: this XML doesn't have line breaks after each tag. the only line breaks are from text content -- enclosed in tags videos = {} while true do line = vlc.readline() if not line then break end -- get playlist name if not playlist_name then -- first <title> tag is for playlist if string.match( line, "<title>([^<]+)</title>" ) then playlist_name = string.match( line, "<title>([^<]+)</title>" ) end end -- check if there's a following XML file with more videos if string.match( line, "<link rel='next' type='application/atom%+xml' href='([^']+)'/>" ) then next_page = string.match( line, "<link rel='next' type='application/atom%+xml' href='([^']+)'/>" ) next_page = vlc.strings.resolve_xml_special_chars( next_page ) -- fix the "&"s end -- get artist of playlist if not playlist_artist then -- first <name> tag is for playlist if string.match( line, "<name>([^<]+)</name>" ) then playlist_artist = string.match( line, "<name>([^<]+)</name>" ) end end -- get video count in playlist if string.match( line, "<openSearch:totalResults>(%d+)</openSearch:totalResults>" ) then video_count = string.match( line, "<openSearch:totalResults>(%d+)</openSearch:totalResults>" ) video_count = tonumber( video_count ) end -- get playlist description if string.match( line, "<media:description [^>]*>([^<]+)</media:description>" ) then playlist_desc = string.match( line, "<media:description [^>]*>([^<]+)</media:description>" ) end -- get all videos from playlist -- NOTE: although using the webpage URL defaults there to only being one video, you can still manually -- enter a different YouTube API playlist feed URL which can have more than one for video_url in string.gmatch( line, "<media:player url='([^']+)'/>" ) do video_item = {} video_item.path = vlc.strings.resolve_xml_special_chars( video_url ) video_item.name = playlist_name .. " (video " .. video_position .. ")" video_item.artist = playlist_artist table.insert( videos, video_item ) video_position = video_position + 1 end end -- process next page of results after current items if there is one if next_page then next_video_position = tonumber( string.match( next_page, "[?&]start%-index=(%d+)" ) ) next_xml = {} next_xml.path = next_page if next_video_position < video_count then next_xml.name = playlist_name .. " (videos " .. next_video_position .. " to " .. video_count .. ")" else next_xml.name = playlist_name .. " (video " .. next_video_position .. ")" end next_xml.description = playlist_desc next_xml.artist = playlist_artist table.insert( videos, next_xml ) end return videos end
edit: Tweaked to support both "youtube.com/user/<user>#(p|g)/c/<playlist_ID>" and "youtube.com/user/<user>#(play|grid)/user/<playlist_ID>" URL formats.
Last edited by uky on 01 Dec 2009 14:22, edited 1 time in total.

3breadt
Big Cone-huna
Big Cone-huna
Posts: 827
Joined: 19 Mar 2006 11:37
Operating System: Win7 Pro / OS X 10.7
Location: Paderborn, Germany
Contact:

Re: Playing Youtube playlists

Postby 3breadt » 30 Nov 2009 00:06

Doesn't work anymore for new playlists on user profiles :(
e.g. http://www.youtube.com/user/<USERID>#p/c/<PLAYLISTID>

Tried fixing it. It loads the playlists but item by item, if I want to get to an item at the end of the playlist its very hindering.

Image

Any idea how to fix that behaviour?

My updated code:

Code: Select all

--[[ Parse YouTube playlists: http://youtube.com/view_play_list?p=<playlist_ID> http://youtube.com/user/<user>#<mode>/c/<playlist_ID> http://gdata.youtube.com/feeds/api/playlists/<playlist_ID> http://gdata.youtube.com/feeds/api/playlists/<playlist_ID>?v=2 TODO: determine if it's feasible to allow easy video browsing through playlist while preventing video URL expiration TODO: add some ability to select different video qualities with &fmt=<quality> --]] function probe() if vlc.access ~= "http" then return false end return ( string.match( vlc.path, "youtube.com/view_play_list%?" ) or string.match( vlc.path, "gdata.youtube.com/feeds/api/playlists/" ) or string.match( vlc.path, "youtube.com/user/[^/]+/c/[0-9A-F]+" ) ) end function parse() -- if webpage URL, redirect to YouTube API v2 XML -- the output from the YouTube API is easier to work with and should change less often than the webpage if string.match( vlc.path, "view_play_list%?" ) or string.match( vlc.path, "youtube.com/user" ) then if string.match( vlc.path, "view_play_list%?" ) then playlist_ID = string.match( vlc.path, "[?&]p=([^&]+)" ) else playlist_ID = string.match( vlc.path, "youtube.com/user/[^/]+/c/([0-9A-F]+)" ) end xml = {} xml.name = "YouTube playlist" xml.path = "http://gdata.youtube.com/feeds/api/playlists/" .. playlist_ID .. "?v=2" -- paging through videos one at a time prevents video URL expiration but now you can't easily browse -- videos out of order... xml.path = xml.path .. "&max-results=1" return { xml } end -- get current position in playlist if string.match( vlc.path, "[?&]start%-index=([^&]+)" ) then video_position = string.match( vlc.path, "[?&]start%-index=([^&]+)" ) else video_position = 1 end -- parse feed XML and let existing YouTube Lua script handle individual videos -- NOTE: this XML doesn't have line breaks after each tag. the only line breaks are from text content -- enclosed in tags videos = {} while true do line = vlc.readline() if not line then break end -- get playlist name if not playlist_name then -- first <title> tag is for playlist if string.match( line, "<title>([^<]+)</title>" ) then playlist_name = string.match( line, "<title>([^<]+)</title>" ) end end -- check if there's a following XML file with more videos if string.match( line, "<link rel='next' type='application/atom%+xml' href='([^']+)'/>" ) then next_page = string.match( line, "<link rel='next' type='application/atom%+xml' href='([^']+)'/>" ) next_page = vlc.strings.resolve_xml_special_chars( next_page ) -- fix the "&"s end -- get artist of playlist if not playlist_artist then -- first <name> tag is for playlist if string.match( line, "<name>([^<]+)</name>" ) then playlist_artist = string.match( line, "<name>([^<]+)</name>" ) end end -- get video count in playlist if string.match( line, "<openSearch:totalResults>(%d+)</openSearch:totalResults>" ) then video_count = string.match( line, "<openSearch:totalResults>(%d+)</openSearch:totalResults>" ) video_count = tonumber( video_count ) end -- get playlist description if string.match( line, "<media:description [^>]*>([^<]+)</media:description>" ) then playlist_desc = string.match( line, "<media:description [^>]*>([^<]+)</media:description>" ) end -- get all videos from playlist -- NOTE: although using the webpage URL defaults there to only being one video, you can still manually -- enter a different YouTube API playlist feed URL which can have more than one for video_url in string.gmatch( line, "<media:player url='([^']+)'/>" ) do video_item = {} video_item.path = vlc.strings.resolve_xml_special_chars( video_url ) video_item.name = playlist_name .. " (video " .. video_position .. ")" video_item.artist = playlist_artist table.insert( videos, video_item ) video_position = video_position + 1 end end -- process next page of results after current items if there is one if next_page then next_video_position = tonumber( string.match( next_page, "[?&]start%-index=(%d+)" ) ) next_xml = {} next_xml.path = next_page if next_video_position < video_count then next_xml.name = playlist_name .. " (videos " .. next_video_position .. " to " .. video_count .. ")" else next_xml.name = playlist_name .. " (video " .. next_video_position .. ")" end next_xml.description = playlist_desc next_xml.artist = playlist_artist table.insert( videos, next_xml ) end return videos end
-- 3breadt (aka altglass)

uky
New Cone
New Cone
Posts: 6
Joined: 18 Aug 2009 00:58

Re: Playing Youtube playlists

Postby uky » 30 Nov 2009 01:47

Doesn't work anymore for new playlists on user profiles :(
e.g. http://www.youtube.com/user/<USERID>#p/c/<PLAYLISTID>

Tried fixing it. It loads the playlists but item by item, if I want to get to an item at the end of the playlist its very hindering.

Any idea how to fix that behaviour?
Huh? Your fix looks correct. Simply tweaking the regex to the new URL format YouTube changed to. It's always loaded one-by-one though. That's because it's just a simple wrapper around the existing "youtube.lua" script which takes a given video URL (e.g. youtube.com/watch?v=<video id>) and returns a video that VLC can play. All my script does is take a playlist and return a list of video URLs for youtube.lua to parse.

The problem with paging through several videos at once is when youtube.lua runs on a URL and gets a corresponding video, it's on an expiration timer. So if you page through the items in the playlist in larger chunks and are watching all the items, it will fail a few videos down the list due to expired videos.

That being said... there's two things you could do. You could feed VLC a specially crafted gdata.youtube.com URL that starts where you want in that long playlist or, if you don't care about video expiration because you don't watch several videos in one sitting or something, you could change how many videos the script pages through at a time.

The prior will just work without having to change the script any. It's a little of a hassle to do but allows the script to work well for long sequential viewing.
So say you're watching a playlist with ID "foo" with 130 items in it and you want to start at position 80.
You could just open this URL in VLC to accomplish that:

Code: Select all

http://gdata.youtube.com/feeds/api/playlists/foo?v=2&start-index=80
If you want to change how many videos to page through at a time, you need to edit this part in the code:

Code: Select all

xml.path = xml.path .. "&max-results=1"
Google allows the max-results parameter to go as high as 50 so you could set it like this for instance:

Code: Select all

xml.path = xml.path .. "&max-results=50"

3breadt
Big Cone-huna
Big Cone-huna
Posts: 827
Joined: 19 Mar 2006 11:37
Operating System: Win7 Pro / OS X 10.7
Location: Paderborn, Germany
Contact:

Re: Playing Youtube playlists

Postby 3breadt » 01 Dec 2009 10:04

Ok thanks. Could your script not just simply fill the playlist with http://www.youtube.com/watch* urls? Then there are no expiring links in the playlist, and when I want to watch a specific video in the playlist the youtube.lua script replaces the "watch" url with the expiring link.
-- 3breadt (aka altglass)

uky
New Cone
New Cone
Posts: 6
Joined: 18 Aug 2009 00:58

Re: Playing Youtube playlists

Postby uky » 01 Dec 2009 14:10

The problem is a Lua playlist script has very limited control over VLC's playlist. All they can really do is append items to the end. Everything else is handled automatically by VLC. I haven't a clue how to do what you suggested or if it's even possible with the current setup. If you read through this thread you'll see I asked for help on that very thing. I never received an answer. ;p

Ah well... I honestly don't even use this script. I wrote it at a time when YouTube's handling of playlists on the website started acting flaky. That was fixed a week later. I just try to maintain this on a whim. These days I just use YouTube's old playlist player that the old-style user channels used by way of a simple Greasemonkey script I wrote: http://userscripts.org/scripts/show/59460 (shameless plug).


Return to “Scripting VLC in lua”

Who is online

Users browsing this forum: No registered users and 26 guests