MythTV master
youtubeXSL_api.py
Go to the documentation of this file.
1# -*- coding: UTF-8 -*-
2
3# ----------------------
4# Name: youtubeXSL_api - XPath and XSLT functions for the mashup grabbers
5# Python Script
6# Author: R.D. Vaughan
7# Purpose: This python script is intended to perform a variety of utility functions
8# for the conversion of data to the MNV standard RSS output format.
9# See this link for the specifications:
10# http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format
11#
12# License:Creative Commons GNU GPL v2
13# (http://creativecommons.org/licenses/GPL/2.0/)
14#-------------------------------------
15__title__ ="youtubeXSL_api - XPath and XSLT functions for the mashup grabbers"
16__author__="R.D. Vaughan"
17__purpose__='''
18This python script is intended to perform a variety of utility functions
19for the conversion of data to the MNV standard RSS output format.
20See this link for the specifications:
21http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format
22'''
23
24__version__="v0.1.0"
25# 0.1.0 Initial development
26
27
28# Specify the class names that have XPath extention functions
29__xpathClassList__ = ['xpathFunctions', ]
30
31# Specify the XSLT extention class names. Each class is a stand lone extention function
32#__xsltExtentionList__ = ['xsltExtExample', ]
33__xsltExtentionList__ = []
34
35import os, sys, re, time, datetime, shutil, urllib.request, urllib.parse, urllib.error, string
36from copy import deepcopy
37import io
38
39
40class OutStreamEncoder(object):
41 """Wraps a stream with an encoder"""
42 def __init__(self, outstream, encoding=None):
43 self.out = outstream
44 if not encoding:
45 self.encoding = sys.getfilesystemencoding()
46 else:
47 self.encoding = encoding
48
49 def write(self, obj):
50 """Wraps the output stream, encoding Unicode strings with the specified encoding"""
51 if isinstance(obj, str):
52 obj = obj.encode(self.encoding)
53 try:
54 self.out.buffer.write(obj)
55 except OSError:
56 pass
57
58 def __getattr__(self, attr):
59 """Delegate everything but write to the stream"""
60 return getattr(self.out, attr)
61
62if isinstance(sys.stdout, io.TextIOWrapper):
63 sys.stdout = OutStreamEncoder(sys.stdout, 'utf8')
64 sys.stderr = OutStreamEncoder(sys.stderr, 'utf8')
65
66try:
67 from io import StringIO
68 from lxml import etree
69except Exception as e:
70 sys.stderr.write('\n! Error - Importing the "lxml" and "StringIO" python libraries failed on error(%s)\n' % e)
71 sys.exit(1)
72
73
74class xpathFunctions(object):
75 """Functions specific extending XPath
76 """
77 def __init__(self):
78 self.functList = ['youtubeTrailerFilter', 'youtubePaging', ]
80 # "trailer 7"
81 re.compile('''^.+?trailer\\ (?P<trailerNum>[0-9]+).*$''', re.UNICODE),
82 # "trailer #7"
83 re.compile('''^.+?trailer\\ \\#(?P<trailerNum>[0-9]+).*$''', re.UNICODE),
84 ]
85 # end __init__()
86
87
92
93 def youtubeTrailerFilter(self, context, *args):
94 '''Generate a list of entry elements that are relevant to the requested search term. Basically
95 remove duplicate and non-relevant search results and order them to provide the best results
96 for the user.
97 Also set the paging variables.
98 Call example: 'mnvXpath:youtubeTrailerFilter(//atm:entry)'
99 return the list of relevant "entry" elements
100 '''
101 searchTerm = common.removePunc('dummy', common.searchterm.lower())
102 titleFilter = etree.XPath('.//atm:title', namespaces=common.namespaces)
103
104 # Remove any leading word "The" from the search term
105 if searchTerm.startswith('the '):
106 searchTerm = searchTerm[4:].strip()
107
108 titleDict = {}
109 for entry in args[0]:
110 titleDict[titleFilter(entry)[0].text] = entry
111
112 # Tag so that there is an order plus duplicates can be easily spotted
113 filteredDict = {}
114 for key in list(titleDict.keys()):
115 title = common.removePunc('dummy', key.lower())
116 if title.startswith('the '):
117 title = title[4:].strip()
118 if searchTerm.find('new ') == -1:
119 title = title.replace('new ', '')
120 if searchTerm.find('official ') == -1:
121 title = title.replace('official ', '')
122 if title.find(searchTerm) != -1:
123 addOns = ''
124 HD = False
125 if searchTerm.find('game ') == -1:
126 if title.find('game') != -1:
127 addOns+='ZZ-Game'
128 if title.find('hd') != -1 or title.find('1080p') != -1 or title.find('720p') != -1:
129 HD = True
130 if title.startswith(searchTerm):
131 addOns+='1-'
132 for regexPattern in self.tailerNum_Patterns:
133 match = regexPattern.match(title)
134 if not match:
135 continue
136 trailerNum = match.groups()
137 if int(trailerNum[0]) < 20:
138 addOns+='Trailer #%s' % trailerNum[0]
139 title = title.replace(('trailer %s' % trailerNum[0]), '')
140 else:
141 addOns+='Trailer #1'
142 break
143 else:
144 if title.find('trailer') != -1:
145 addOns+='Trailer #1'
146 if HD and not addOns.startswith('ZZ-Game'):
147 if addOns:
148 addOns='HD-'+addOns
149 else:
150 addOns='YHD'
151 for text in ['hd', 'trailer', 'game', '1080p', '720p']:
152 title = title.replace(text, '').replace(' ', ' ').strip()
153 filteredDict[('%s %s' % (addOns, title)).strip()] = titleDict[key]
154
155 # Get rid of obvious duplicates
156 filtered2Dict = {}
157 sortedList = sorted(filteredDict.keys())
158 for index in range(len(sortedList)):
159 if index == 0:
160 filtered2Dict[sortedList[index]] = deepcopy(filteredDict[sortedList[index]])
161 continue
162 if sortedList[index] != sortedList[index-1]:
163 filtered2Dict[sortedList[index]] = deepcopy(filteredDict[sortedList[index]])
164
165 # Copy the remaining elements to a list
166 finalElements = []
167 sortedList = sorted(filtered2Dict.keys())
168 for index in range(len(sortedList)):
169 titleFilter(filtered2Dict[sortedList[index]])[0].text = '%02d. %s' % (index+1, titleFilter(filtered2Dict[sortedList[index]])[0].text)
170 finalElements.append(filtered2Dict[sortedList[index]])
171
172 # Set the paging values
173 common.numresults = str(len(finalElements))
174 common.returned = common.numresults
175 common.startindex = common.numresults
176
177 return finalElements
178 # end youtubeTrailerFilter()
179
180 def youtubePaging(self, context, args):
181 '''Generate a page value specific to the mashup search for YouTube searches
182 Call example: 'mnvXpath:youtubePaging('dummy')'
183 The page value is some times a page # and sometimes an item position number
184 return the page value that will be used in the search as a string
185 '''
186 return str((int(common.pagenumber) -1) * common.page_limit + 1)
187 # end youtubeTrailerFilter()
188
189
194
195
200
201
202
def youtubeTrailerFilter(self, context, *args)
Start of XPath extension functions.