Playing Youtube playlists

Discuss your Lua playlist, album art and interface scripts.

Playing Youtube playlists

Postby neowam on Wed Sep 17, 2008 2:57 pm

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?
neowam
New Cone
New Cone
 
Posts: 1
Joined: Wed Sep 17, 2008 2:56 pm

Re: Playing Youtube playlists

Postby dionoea on Wed Sep 17, 2008 3:52 pm

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)
dionoea
Developer
Developer
 
Posts: 5157
Joined: Thu Dec 04, 2003 12:09 am
Location: Paris, France

Re: Playing Youtube playlists

Postby uky on Tue Aug 18, 2009 1:48 am

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.
uky
New Cone
New Cone
 
Posts: 4
Joined: Tue Aug 18, 2009 12:58 am

Re: Playing Youtube playlists

Postby ducati67 on Sat Aug 22, 2009 4:39 am

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
ducati67
New Cone
New Cone
 
Posts: 1
Joined: Tue Aug 18, 2009 11:43 pm

Re: Playing Youtube playlists

Postby uky on Sat Aug 22, 2009 8:16 am

ducati67 wrote: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.

ducati67 wrote: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 "&amp;"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: 4
Joined: Tue Aug 18, 2009 12:58 am

Re: Playing Youtube playlists

Postby uky on Sat Aug 22, 2009 12:24 pm

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.
uky
New Cone
New Cone
 
Posts: 4
Joined: Tue Aug 18, 2009 12:58 am

Re: Playing Youtube playlists

Postby david4utv on Sun Oct 11, 2009 10:24 pm

how can i use (activate) lua from a libvlc object (i am using c#)...
david4utv
New Cone
New Cone
 
Posts: 5
Joined: Sun Oct 11, 2009 10:19 pm

Re: Playing Youtube playlists

Postby uky on Sun Oct 11, 2009 10:53 pm

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/<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/[^/]+/user/[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/[^/]+/user/([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 "&amp;"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

uky
New Cone
New Cone
 
Posts: 4
Joined: Tue Aug 18, 2009 12:58 am


Return to Scripting VLC in lua

Who is online

Users browsing this forum: No registered users and 1 guest