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.