URL Service

I still do not get how I am supposed to "follow the URL provided in the key?" 

Granted, I am currently going through a couple tutorials to better understand the args and kwargs as it relates to python, so I am still getting a grasp of that.  But the key pretty much just says it is calling the PlayVideo function of your URL Service with a long list of letters and numbers returned as arguments for that function.  How does that translate to a URL?

I tried a couple different URL services, and some have the added video info like audio channels and bitrate, but they all just direct you to the PlayVidoe function with long arguments.

For example, I put a video from the Florida Fishing Report (since it was a good example of a basic site) into the test url as follows:

http://localhost:32400/system/services/url/lookup?url=http%3A%2F%2Fwww.floridainsiderfishingreport.com%2Ftheme-species-sailfish-part-1-2

And what it returned was a key like this followed by some info about the video quality:

/:/plugins/com.plexapp.system/serviceFunction/url/com.plexapp.plugins.floridafishingreport/Florida%20Fishing%20Report/PlayVideo?args=Y2VyZWFsMQoxCnR1cGxlCjAKcjAK&kwargs=Y2VyZWFsMQoxCmRpY3QKMQpzNzQKaHR0cDovL3d3dy5mbG9yaWRhaW5zaWRlcmZpc2hpbmdyZXBvcnQuY29tL3RoZW1lLXNwZWNpZXMtc2FpbGZpc2gtcGFydC0xLTJzMwp1cmxyMAo_

How is that a URL for the location of the video?

Take the key and tack it on to the url:port of your PMS. Follow that, and it should provide you with a.) a redirect to a video file, b.) more xml with details, or c.) an error and traceback (the traceback may only appear in the logs depending on the severity of the error).

'http://localhost:32400/:/plugins/com.plexapp.system/serviceFunction/url/com.plexapp.plugins.floridafishingreport/Florida%20Fishing%20Report/PlayVideo?args=Y2VyZWFsMQoxCnR1cGxlCjAKcjAK&kwargs=Y2VyZWFsMQoxCmRpY3QKMQpzNzQKaHR0cDovL3d3dy5mbG9yaWRhaW5zaWRlcmZpc2hpbmdyZXBvcnQuY29tL3RoZW1lLXNwZWNpZXMtc2FpbGZpc2gtcGFydC0xLTJzMwp1cmxyMAo_'

Ok, that makes sense.  Thanks.  I had seen the resulting xml page from this type of URL shown in posts, but there was no explanation of how they were able to pull up this page. So thank you for the fact that I need to add the "http://localhost:32400" to the beginning of the key.

Hey guys, as I have mentioned on another post, I am creating some very basic beginner template pages for each part of the channel programming.  In these templates, I have included explanation and comments based on all the great advice you guys have given me through the process of writing my first URL service.

This is mainly for my own benefit because my memory is horrible, but I figured I would share them in case there may be other newbies who have run into similar problems and/or questions as I have when first learning to write channels. 

Now that I have gotten through writing a basic URL service, I wanted to post the URL Service templates I created for ServiceInfo.plist and ServiceCode.pys to the template topic I created.  But first, I wanted to post it here and get any feed back you guys may have about any part of these templates that I may need to tweak.

Sorry, I know this is a long post, but any feedback you guys may have is greatly appreciated.

Thanks in advanced for your help. And thanks for all the help you guys gave me figuring out how to write a URL service.

ServiceInfo.plist:

<?xml version="1.0" encoding="UTF-8"?>




  URL
  
  
    Your Unique Name
    
    
      URLPatterns
      
      
        http://([^.]+.)?domainname/.+ 		
      
      
      
      TestURLs
      
        http://www.anywebsite.com/anyfolder/anyparticularpage.html
      
    
  



And ServiceCode.pys:

# IMPORTANT NOTE: AS WITH ALL PYTHON CODING, PROPER INDENTATION IS NECESSARY TO PREVENT ERRORS.
# PROGRAMS LIKE NOTEPAD++ DO NOT ALWAYS LINE UP PROPERLY OR CATCH THESE INDENTION ERRORS. TO
# PREVENT THESE AND OTHER PYTHON ERRORS, BEFORE RUNNING THE PROGRAM IN PLEX OR AFTER MAKING CHANGES
# TO THE FILE, I OPEN IT FIRST IN A LOCAL VERSION OF PYTHON I HAVE LOADED AND CHOOSE
# RUN > CHECK MODULE TO FIND ANY POSSIBLE PROBLEMS.

# First you create any python variables for use later in this program. The naming and values are
# based on the where and how you choose to use them in your code. For the most part, you are
# defining variables you will use in the PlayVideo function. Often video files are stored in a
# video directory on the site, but the base file could just be htpp://www.anywebsite.com/
BASE_URL = 'http://video.anywebsite.com/'
# The variable below is basic regex to pull a video from an html page.  I show its use in a 
# optional version of the PlayVideo function
# RE_VIDEO_URL = Regex('videofile:"(?P[^"]+)"')

# IMPORTANT NOTE: THE VALUE OF 'URL' THAT IS PASSED TO THE DIFFERENT FUNCTIONS IN THIS PROGRAM IS
# DETERMINED EITHER WITHIN THE PROGRAMMING OF THE INDIVIDUAL CHANNEL PLUGIN THAT USES THIS URL
# SERVICE OR BY THE END USER CHOOSING THE PLEXIT BUTTON

########################################################################################################
# BELOW IS AN OPTIONAL CODE FOR CONVERTING HTML VIDEO PAGES TO THEIR CORRESPONDING VIDEO INFO XML PAGES.
# IF YOU CAN FIND REFERENCE TO THESE XML VIDEO INFO FILES ON THE VIDEO WEBPAGES, THESE XML PAGES CAN BE
# USED FOR EASY RETRIEVAL OF METADATA AND VIDEO FILES USING XML.ElementFromURL.
# We use string.replace() to create a new url that adds /xml/video-folder/ to middle and changes the extension to .xml.  
########################################################################################################

XML_URL = url.replace('http://www.anywebsite.com/','http://www.anywebsite.com/xml/videos-page/').replace('.html','.xml')

####################################################################################################
# This pulls the metadata that is used for Plexit, suggested videos (to friends), the pre-play 
# screen on iOS, Roku, Android and Plex/Web, as well as if you get the "info" or "details" within
# the OSX/Windows/Linux desktop client. Afer you pull the data you should save it to preset
# variables.  These basic variables are title, summary, and thumb, but some also include duration,
# and originally_available_at, content_rating, show, tags and index.

def MetadataObjectForURL(url):

# Here you are using the ElementFromURL() API to parse or pull up all the data from the webpage. 
# See the Framework documentation API reference for all the choices for parsing data
	page = HTML.ElementFromURL(url)

	# Extract the data available on the page using xpath commands. Below is a basic example
        # that pulls the the title, description and thumb from the head of a html document that
        # makes a request of this URL Service
	title = page.xpath("//head//meta[@property='og:title']")[0].get('content')
	description = page.xpath("//head//meta[@property='og:description']")[0].get('content')
	thumb = page.xpath("//head//meta[@property='og:image']")[0].get('url')	

	# This command returns the metadata values you extracted as a video clip object.  
	# See the Framework documentation Objects API reference for all the choices
	return VideoClipObject(
		title = title,
		summary = description,
		thumb = thumb
	)

####################################################################################################
# Here you will define the different types of videos available on this website and the values for 
# their quality. You will need to search the source of the web pages that play a video to determine
# the type and location of videos offered thru the site. You can see if the name and location is 
# available through an embed link, but you may have to look other code in the web page like 
# javascript to find this information. You will also need this information later when writing the
# code for the PlayVideo function that pulls these specific video files from the webpage

def MediaObjectsForURL(url):
    return [

# First you are calling a MediaObject() for each type of video the website offers using the 
# MediaObject API command. Most separate these types of videos by the resolution, for example a 
# site may offer a high and low quality option for each video or they may offer an .flv and a .mp4
# version of the video file. You can choose to only offer one type of video file or give the end 
# user the option of choosing the type of video they want to use if there are several different
# types and qualities.
        MediaObject(

# Then within each MediaObject you define the values for that particular type of video
# The options most used are video_codec, audio_codec, parts, container, video_resolution, 
# audio_channels, and bitrate. See the Framework documentation API reference for a lists all 
# possible attributes for MediaObject(). Audio Codecs are AAC and .MP3; Video Codecs are .H264; 
# Container File Types are .MP4, .MKV, .MOV, and .AVI and for audio channels stereo equals 2

# I have found the best way to determine these attributes is to use VLC player and open the network
# stream URL of a few of the videos available on the site. And then use the tools to view the media 
# information esp. codec info

	    video_codec = VideoCodec.H264,
	    audio_codec = AudioCodec.MP3,
	    video_resolution = '720',
	    audio_channels = 2,
	    container = 'f4v',

# This section of the code allows you to peice together videos that come in 2 or more parts it also
# uses a callback function that calls the PlayVideo function below. This done to separate the
# playing of the video from the call for the media object. That way it will only play the video if
# the user selects that particular video. The code below is the basic format that all URL Serivces 
# use and then they define PlayVideo if the video only has one part, it only needs one PartObject 
# Note that we are also sending a fmt variable to tell the PlayVideo function whether this is a
# high or low quality video.  This is only necessary when you have more than one choice for the video files available.
			
            parts = [PartObject(key=Callback(PlayVideo, url = url, fmt='hi')]
	)
# Below we are doing the same as above, and defining a second MediaObject that is a low quality
# version of the video file that is available. The fmt variable is sent to the PlayVideo function
# to help you determine which of the available video types to send back to the Media Object.
	MediaObject(
	    video_codec = VideoCodec.H264,
	    audio_codec = AudioCodec.MP3,
	    video_resolution = '420',
	    audio_channels = 2,
	    container = 'f4v',
	    parts = [PartObject(key=Callback(PlayVideo, url = url, fmt='lo')]


	)
    ]

####################################################################################################
# Here we are defining the PlayVideo function we called in the callback function above. This
# function defines the pattern for the location and naming scheme of the video so we can play the
# video file directly. You use HTML request, regular expressions, and predefined variables to
# create the path or http address of the video associated with the html or xml page that was sent 
# to this service through the "URL" value. The programming here will vary greatly based on the
# location of the video file related to your video pages. This is where you will be doing the
# majority to the programming. It is best to refer to other services already created that may be 
# similar to the website you are creating the URL service for to see examples of how to pull the
# video file.

# First we define the function taking the the variables for the url entered into the service and the
# fmt variable we established above in MediaObjects
def PlayVideo(url, fmt):

# Below I have included a basic example of how to program the PlayVideo function for pulling the
# video location on the web page whose URL was sent to the service and only has ONE video on the
# page. The code pulls the raw data from the web page using the URL and then uses a simple page
# search that returns an f4v video file url that is located in each html video page. It uses both
# of the variables we established at the top of the service.
# EXAMPLE:
#  page = HTTP.Request(url).content
# 	video = RE_VIDEO_URL.search(page).group('video_url') + ".f4v"
#	video = BASE_URL + video


# The example I chose to use for this function uses the optional xml video information file to pull
# the high and low quality version of a video from the url sent to this URL service. It basically
# pulls the elements from the URL, looks for scripts within the page and finds one that mentions
# xml videos when it finds a match it pulls the address of the corresponding xml video info page
# out of that script. The function then opens that xml video info page and uses xpath commands to
# extract the video URL and returns those values in the form of a high and low quality video. Then
# it uses the fmt variable to determine which of these videoss should be the value of video.

    html = HTML.ElementFromURL(url, cacheTime=CACHE_1HOUR)
   
    for script in html.xpath('//script[@type="text/javascript"]'):
        text = HTML.StringFromElement(script)
        match = re.search("(/xml/video.+)',(\.*)", text)
        if match:
            xmlurl = match.group(1)
            xmlurl = xmlurl.replace("',", '')
            Log('the value of xmlurl is%s'%xmlurl)
            xmlurl = base_url + xmlurl
           
            xml = XML.ElementFromURL(xmlurl)
            Log('the value of xml is%s'%xml)
            if xml:
                # Here we are extracting the video file addresses from the xml video file metadata
                videoHi = xml.xpath('//video/videoHi//text()')[0]
                Log('the value of videoHi is%s'%videoHi)
                videoLo = xml.xpath('//video/videoLo')[0].text
								
                videoHi = videoHi.replace('.f4v', '.mp4')
                videoLo = videoLo.replace('.f4v', '.mp4')

            if fmt == 'hi':
                video = videoHi
            else:
                video = videoLo
            
            # Once we have the first video match, don't look at any other matches.
            break


# We then return the address or value of the video. This video URL is then sent back to the
# MediObject above with return Redirect(video). The Redirect() is a Plex Framework function that
# essentially tells the Client to look at this new URL for the actual media file. 

return Redirect(video)

####################################################################################################
def TestURLs():

# This is for testing the URL, so you are making sure the URL service is working.
# YOU DO NOT NEED THIS IF YOU PUT A TEST URL IN YOUR SERVICEINFO.PLIST FILE.  THIS IS AN
# ALTERNATIVE WAY IF YOU THINK THE VIDEO PAGES WILL CHANGE A LOT AND YOU DO NOT THINK YOU CAN
# DETERMINE A SPECIFIC URL THAT WILL NOT CHANGE.
# This coding below is a basic example that allows you to make the test url more dynamic so the
# service will not fail just because the test url no longer exists. Enter the main domain for urls
# that will be sent to this service in place of anydomain.com

	test_urls = []
	page = HTML.ElementFromURL('http://www.anydomain.com/')

	# here in this link command you need to edit this xpath to reflect the id or class is used
        # to represent a video player container on pages for this url service and enter the correct
        # domain name
	for link in page.xpath("//a/span[@class='vid']/.."):
		if len(test_urls) < 3:
		url = link.get('href')
		url = "http://www.anydomain.com" + url

	if url not in test_urls:
		test_urls.append(url)
	else:
		break

	return test_urls

##########################################################################
#                  TESTING YOUR URL SERVICE                              #
##########################################################################
# To test your url service, the best way is to use 'http://localhost:32400/system/services/url/lookup?url='
# followed by an html page that contains a video. You must first use a URL encoder like
# 'http://meyerweb.com/eric/tools/dencoder/' to give the page name the proper format.  Paste this
# into the address bar of your browser. This will result in a page that shows you the metadata and
# a key with video info. If this info does not come up, check the system log for errors. 
# To see the actual video file url and any errors with playing the video, you will then cut the key from the resulting metadata page you pulled up above and add 'http://localhost:32400' to the
# beginning of it and paste it into the address bar of your browser. The resulting page will show
# you the video file location and any errors in locating it.


 

I was looking at a couple of sites. The Xfinity and Dish Anywhere sites and I can see that both seem to use Hulu for part of their videos.  As of yet, I do not have an Online ID to test the Xfinity site, but I do with the Dish Anywhere.  I was able to determine that on the Dish Anywhere site some of the videos use Hulu and some use a .smil to access a video server hosted by Dish. 

I need to go back and do some research on the best way to pull video info from .smil files, but I do remember seeing some URL services that did that.  But before I even consider writing a URL service for this type of site, I just want to know how you would handle a site that pulls their videos from multiple locations, so I will know if this is even a project I want to attempt.

I would think that you would focus on pulling the videos from the Dish video location that uses the .smil file in your URL service. How would you create the URL service knowing that some of the videos pull from Hulu? Would Plex know these videos use the existing Hulu URL service? Would you have to add additional code to your URL service to redirect those particular videos to the Hulu URL service? Or would you have to copy the Hulu URL service code into the Dish URL service code to make it work?

The answer to most of your questions is"it depends". If there is a straightforward way to differentiate from the URL whether the video is hosted by Dish or Hulu, then you could use the NormalizeURL function to manipulate the URL and pass back a Hulu URL for the pages that use Hulu videos. Then the Hulu URL service will take over handling. If you can’t determine whether the page uses a Hulu video without making one or more HTTP requests, then you’ll have to add some code in to differentiate the source of the video and the pass off to the appropriate URL service. The URL service for TED is one example that comes to mind where multiple hostings services may be used but we can’t determine the source of the content until after the page is loaded. You shouldn’t need to copy the Hulu URL service code into your own service.

Thanks for the info

I would have to really look at the json files to know for sure, but just looking at the portion of a page playing a Hulu video, it has an embed code for the video that shows the source as a Hulu address, so it would probably not need any special programming for the Hulu videos.

Though I now get the basic concepts of writing a URL service, I still feel it is a weak point for me.  So I want to play with them a little more to make sure I get the concept of them a little better. And sorry if this post seems a little all over the place, I think this is one of those concepts I am having problems grasping and once I figure this out, URL services will make more sense to me.

The main thing you have to figure out is the PlayVideo function to access the actual video file. I am thinking there are a limited list of ways in which these sites connect the video file location to their webpage. I understand some of the more basic ways, but I was wondering, from those who have more experience writing URL services, if there is a set list of ways they connect.

For example, the most basic would be a direct link to the video file within the web page, but you do not see that very often. I have also encountered ones that use an xml video page that gives all the data for the video and its location. 

Though only in concept, I also get the idea of .smil files that contain video information. This is the type I will probably play with more next, since I am thinking, this would be the next hardest in degree of difficulty. I had looked into the Dish Anywhere page and found how it connects to a .smil file, but was unable to get a video file address to play directly for more that a few seconds. I think it may have something to do with the Google's Widevine Video Optimizer the site uses to play its videos.

So my question is are there set ways in which websites connect to the video files? What are some of the other methods used?

I have seen some that appear to use cloud hosting sites, which to me looks like it would be the most difficult to write the PlayVideo function for. I also noticed there was already a Brightcove URL service. Since that is the largest cloud service used, can you write a URL service that connects to that existing URL service?

Also, could anyone suggest a good basic example of an existing URL service that accesses a .smil file for me to use for example?

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.