1 import os
, sys
, socket
, re
, urllib
, zlib
2 from Cheetah
.Template
import Template
3 from plugin
import Plugin
, quote
, unquote
4 from urlparse
import urlparse
5 from xml
.sax
.saxutils
import escape
6 from lrucache
import LRUCache
7 from UserDict
import DictMixin
8 from datetime
import datetime
, timedelta
17 from Cheetah
.Filters
import Filter
19 SCRIPTDIR
= os
.path
.dirname(__file__
)
21 CLASS_NAME
= 'Youtube'
23 default
= "http://gdata.youtube.com/feeds/api/standardfeeds/top_rated"
25 # Preload the templates
26 tcname
= os
.path
.join(SCRIPTDIR
, 'templates', 'container.tmpl')
27 ttname
= os
.path
.join(SCRIPTDIR
, 'templates', 'TvBus.tmpl')
28 txname
= os
.path
.join(SCRIPTDIR
, 'templates', 'container.xsl')
29 CONTAINER_TEMPLATE
= file(tcname
, 'rb').read()
30 TVBUS_TEMPLATE
= file(ttname
, 'rb').read()
31 XSL_TEMPLATE
= file(txname
, 'rb').read()
34 # subprocess is broken for me on windows so super hack
35 def patchSubprocess():
36 o
= subprocess
.Popen
._make
_inheritable
38 def _make_inheritable(self
, handle
):
39 if not handle
: return subprocess
.GetCurrentProcess()
40 return o(self
, handle
)
42 subprocess
.Popen
._make
_inheritable
= _make_inheritable
44 mswindows
= (sys
.platform
== "win32")
53 os
.kill(pid
, signal
.SIGTERM
)
57 handle
= ctypes
.windll
.kernel32
.OpenProcess(1, False, pid
)
58 ctypes
.windll
.kernel32
.TerminateProcess(handle
, -1)
59 ctypes
.windll
.kernel32
.CloseHandle(handle
)
62 if path
.find("?") == -1:
67 class Youtube(Plugin
):
68 CONTENT_TYPE
= 'x-container/tivo-videos'
70 videos_feed
= "http://gdata.youtube.com/feeds/api/videos"
71 watch_video
= "http://www.youtube.com/watch"
72 get_video
= 'http://www.youtube.com/get_video'
73 standardfeeds
= "http://gdata.youtube.com/feeds/api/standardfeeds/%s"
74 standardfeedsregion
= "http://gdata.youtube.com/feeds/api/standardfeeds/%s/%s"
75 user_feed
= "http://gdata.youtube.com/feeds/api/users/%s/%s"
76 playlist
= "http://gdata.youtube.com/feeds/api/playlists/%s"
82 # from multiprocessing import Process, Lock
83 # p = Process(target=self.LoadVideos)
86 # except(ImportError):
91 #logging.debug('Starting Youtube Plugin Cache')
92 #self.QueryContainer()
95 #def LoadVideos(self):
97 # time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime())
98 # for k,v in videos.iteritems():
100 def send_file(self
, handler
, container
, name
):
101 if handler
.headers
.getheader('Range') and \
102 handler
.headers
.getheader('Range') != 'bytes=0-':
103 handler
.send_response(206)
104 handler
.send_header('Connection', 'close')
105 handler
.send_header('Content-Type', 'video/x-tivo-mpeg')
106 handler
.send_header('Transfer-Encoding', 'chunked')
107 handler
.end_headers()
108 handler
.wfile
.write("\x30\x0D\x0A")
111 def between(data
, From
, To
):
112 start
= data
.find(From
)
116 end
= data
.find(To
, start
)
119 return data
[start
:end
]
121 page
= urllib
.urlopen(self
.watch_video
+ "?v=%s" % handler
.path
.split("/")[2]).read()
122 ticket
= between(page
, '"t": "', '"')
124 handler
.send_error(404)
127 url
= self
.get_video
+ "?video_id=%s&t=%s" % (handler
.path
.split("/")[2], ticket
)
131 if container
.has_key("fmt"):
132 url
= url
+ "&fmt=%s" % (config
.get(container
, "fmt"))
134 logging
.debug('download url is %s' % url
)
135 handler
.send_response(200)
136 handler
.end_headers()
138 settings
= '-vcodec mpeg2video -r 29.97 -b 4096K -maxrate 17408k -bufsize 1024k -aspect 4:3 -s 544x480 -ab 192k -ar 48000 -acodec mp2 -ac 2 -f vob -'
140 url
= urllib
.urlopen(url
).geturl()
141 cmd
= [config
.get('Server', 'ffmpeg'), '-i', url
] + settings
.split(' ')
142 logging
.debug('transcoding using ffmpeg command:')
143 logging
.debug(' '.join(cmd
))
144 ffmpeg
= subprocess
.Popen(cmd
, bufsize
=(512 * 1024), stdout
=subprocess
.PIPE
)
146 cmd
= [config
.get('Server', 'ffmpeg'), '-i', '-'] + settings
.split(' ')
147 source
= urllib
.urlopen(url
)
148 logging
.debug('transcoding using ffmpeg command:')
149 logging
.debug(' '.join(cmd
))
150 ffmpeg
= subprocess
.Popen(cmd
, bufsize
=(512 * 1024), stdout
=subprocess
.PIPE
, stdin
=source
)
153 shutil
.copyfileobj(ffmpeg
.stdout
, handler
.wfile
)
159 def __duration(self
, full_path
):
160 return transcode
.video_info(full_path
)['millisecs']
162 def __est_size(self
, full_path
, tsn
= ''):
163 return int(full_path
.duration
) * 54672
165 def getVideo(self
, entry
):
167 for contentlink
in entry
.getElementsByTagName("media:content"):
168 video
['format'] = contentlink
.attributes
["yt:format"].value
169 video
[video
['format']] = contentlink
.attributes
["url"].value
170 video
['type'] = contentlink
.attributes
["type"].value
# always application/x-shockwave-flash ?
171 video
['medium'] = contentlink
.attributes
["medium"].value
172 video
['duration'] = contentlink
.attributes
["duration"].value
174 nodes
= ['summary', 'id', 'published', 'title', 'updated', 'yt:aboutMe', 'yt:age', 'yt:books', 'yt:company', 'yt:countHint', 'yt:firstName', 'yt:gender', 'yt:hobbies', 'yt:hometown', 'yt:lastName', 'yt:location', 'yt:movies', 'yt:music', 'yt:noembed', 'yt:occupation', 'yt:playlistId', 'yt:playlistTitle', 'yt:position', 'yt:private', 'yt:queryString', 'yt:recorded', 'yt:relationship', 'yt:school', 'yt:uploaded', 'yt:username', 'yt:status', 'yt:videoid', 'media:category', 'media:description', 'media:keywords', 'media:rating', 'media:restriction', 'media:title', 'gml:pos', 'app:edited']
175 for nodename
in nodes
:
177 node
= entry
.getElementsByTagName(nodename
)[0]
178 if nodename
.find(":")!=-1:
179 nodename
= nodename
.split(":")[1]
180 if node
.nodeType
== node
.ELEMENT_NODE
:
181 video
[nodename
] = node
.firstChild
.data
182 elif node
.nodeType
== node
.TEXT_NODE
:
183 video
[nodename
] = node
.data
184 except(IndexError, AttributeError):
185 if nodename
.find(":")!=-1:
186 nodename
= nodename
.split(":")[1]
187 video
[nodename
] = False
189 if entry
.getElementsByTagName("yt:state"):
190 video
['playable'] = False
192 video
['playable'] = True
194 if video
['playlistId']:
195 video
['id'] = video
['playlistId']
196 video
['group'] = True
197 video
['title'] = self
.getTitle(entry
)
198 url
= self
.playlist
% video
['id']
199 feed
= xml
.dom
.minidom
.parseString(urllib
.urlopen(url
).read())
200 video
['type'] = "playlist"
201 video
['isdir'] = True
202 logging
.debug('Getting playlist %s at %s' % (video
['title'], url
))
203 self
.videos
.update({video
['id']: self
.getVideos(feed
)})
204 video
['total_items'] = len(self
.videos
[video
['id']])
205 elif video
['videoid']:
206 video
['id'] = video
['videoid']
207 if entry
.getElementsByTagName('gd:rating'):
208 rating
= entry
.getElementsByTagName("gd:rating")[0]
209 video
['rating'] = rating
.getAttribute('average')
211 video
['rating'] = '3'
212 video
['type'] = "video"
213 video
['isdir'] = False
214 video
['duration'] = entry
.getElementsByTagName("yt:duration")[0].attributes
["seconds"].value
215 if video
['keywords']:
216 video
['vProgramGenre'] = video
['keywords'].split(", ")
217 video
['channelnumber'] = "0" # maybe have different channels for each youtube channel?
218 video
['channelname'] = "YOUTUBE" # maybe have different names for each youtube channel?
219 video
['showingBits'] = "1" # change this for different youtube ratings
220 video
['displayMinorNumber'] = "111"
221 video
['episodeTitle'] = video
['title']
222 video
['seriesTitle'] = video
['title']
223 video
['isEpisode'] = 'false'
224 video
['vChoreographer'] = []
225 #author = entry.getElementsByTagName("author")[0]
226 #video['author'] = author.getElementsByTagName("name")[0].firstChild.data
227 #video['author.uri'] = author.getElementsByTagName("uri")[0].firstChild.data
228 video
['author'] = entry
.getElementsByTagName("media:credit")[0].firstChild
.data
229 video
['vActor'] = [video
['author']]
230 video
['vExecProducer'] = []
231 video
['vGuestStar'] = []
232 video
['vSeriesGenre'] = []
234 video
['vWriter'] = []
235 video
['vProducer'] = []
236 video
['showType'] = ('SERIES', '5')
237 video
['tvRating'] = "5"
239 video
['starRating'] = str(round(float(video
['rating']) * 7 / 5)).split(".")[0]
241 video
['starRating'] = '4'
242 video
['vDirector'] = []
243 video
['mpaaRating'] = "N8"
244 video
['episodeNumber'] = '1'
245 video
['seriesId'] = ""
247 duration_delta
= timedelta(milliseconds
=int(video
['duration'])*1000)
250 from xml
.utils
import iso8601
251 date
= iso8601
.parse(video
['uploaded'].strip())
252 utcdate
= datetime
.utcfromtimestamp(date
)
253 video
['time'] = utcdate
.isoformat()
254 video
['startTime'] = utcdate
.isoformat()
255 video
['stopTime'] = utcdate
+duration_delta
257 now
= datetime
.utcnow()
258 video
['time'] = now
.isoformat()
259 video
['startTime'] = now
.isoformat()
260 video
['stopTime'] = (now
+ duration_delta
).isoformat()
262 #video['originalAirDate'] = video['uploaded'].replace(".000Z", "")
263 video
['movieYear'] = video
['uploaded'].split("T")[0].split("-")[0]
265 min = duration_delta
.seconds
/ 60
266 sec
= duration_delta
.seconds
% 60
269 video
['iso_duration'] = 'P' + str(duration_delta
.days
) + \
270 'DT' + str(hours
) + 'H' + str(min) + \
272 video
['size'] = int(video
['duration']) * 546720
273 video
['milliseconds'] = int(video
['duration']) * 1000
274 elif video
['username']:
275 folder
['group'] = True
276 url
= entry
.getElementsByTagName("content")[0].getAttribute('src')
277 feed
= xml
.dom
.minidom
.parseString(urllib
.urlopen(url
).read())
278 video
['title'] = self
.getTitle(feed
)
279 video
['id'] = video
['username']
280 video
['type'] = "subscription"
281 video
['isdir'] = True
282 self
.videos
[video
['id']] = self
.getVideos(feed
)
283 video
['total_items'] = len(self
.videos
[video
['id']])
284 elif entry
.getElementsByTagName("media:player"):
285 video
['id'] = entry
.getElementsByTagName("media:player")[0].getAttribute('url').replace('http://www.youtube.com/watch?v=', '')
286 rating
= entry
.getElementsByTagName("gd:rating")[0]
287 video
['rating'] = rating
.getAttribute('average')
288 video
['type'] = "video"
289 video
['isdir'] = False
290 video
['duration'] = entry
.getElementsByTagName("yt:duration")[0].attributes
["seconds"].value
291 video
['vProgramGenre'] = video
['keywords'].split(", ")
292 video
['channelnumber'] = "0" # maybe have different channels for each youtube channel?
293 video
['channelname'] = "YOUTUBE" # maybe have different names for each youtube channel?
294 video
['showingBits'] = "1" # change this for different youtube ratings
295 video
['displayMinorNumber'] = "111"
296 video
['episodeTitle'] = video
['title']
297 video
['seriesTitle'] = video
['title']
298 video
['isEpisode'] = 'false'
299 video
['vChoreographer'] = []
300 author
= entry
.getElementsByTagName("author")[0]
301 video
['author'] = author
.getElementsByTagName("name")[0].firstChild
.data
302 #video['author.uri'] = author.getElementsByTagName("uri")[0].firstChild.data
303 video
['vActor'] = [video
['author']]
304 video
['vExecProducer'] = []
305 video
['vGuestStar'] = []
306 video
['vSeriesGenre'] = []
308 video
['vWriter'] = []
309 video
['vProducer'] = []
310 video
['showType'] = ('SERIES', '5')
311 video
['tvRating'] = "5"
313 video
['starRating'] = str(round(float(video
['rating']) * 7 / 5)).split(".")[0]
315 video
['starRating'] = '4'
316 video
['vDirector'] = []
317 video
['mpaaRating'] = "N8"
318 video
['episodeNumber'] = '1'
319 video
['seriesId'] = ""
321 duration_delta
= timedelta(milliseconds
=int(video
['duration'])*1000)
324 from xml
.utils
import iso8601
325 date
= iso8601
.parse(video
['uploaded'].strip())
326 utcdate
= datetime
.utcfromtimestamp(date
)
327 video
['time'] = date
.isoformat()
328 video
['startTime'] = date
.isoformat()
329 video
['stopTime'] = date
+duration
331 now
= datetime
.utcnow()
332 video
['time'] = now
.isoformat()
333 video
['startTime'] = now
.isoformat()
334 video
['stopTime'] = (now
+ duration_delta
).isoformat()
336 #video['originalAirDate'] = video['uploaded'].replace(".000Z", "")
337 video
['movieYear'] = video
['updated'].split("T")[0].split("-")[0]
339 min = duration_delta
.seconds
/ 60
340 sec
= duration_delta
.seconds
% 60
343 video
['iso_duration'] = 'P' + str(duration_delta
.days
) + \
344 'DT' + str(hours
) + 'H' + str(min) + \
346 video
['size'] = int(video
['duration']) * 546720
347 video
['milliseconds'] = int(video
['duration']) * 1000
349 logging
.debug('The entry named %s probably does not have proper pyTiVo Youtube Plugin support' % video
['title'])
354 def getVideos(self
, feed
, offset
=0):
356 for entry
in feed
.getElementsByTagName("entry"):
357 video
= self
.getVideo(entry
)
358 if video
: videos
.append(video
)
361 def getTitle(self
, feed
):
362 return feed
.getElementsByTagName("title")[0].firstChild
.data
364 def item_count(self
, query
, files
, last_start
=0):
365 """Return only the desired portion of the list, as specified by
366 ItemCount, AnchorItem and AnchorOffset. 'files' is either a
367 list of objects with an 'id' attribute.
369 totalFiles
= len(files
)
372 if totalFiles
and query
.has_key('ItemCount'):
373 count
= int(query
['ItemCount'][0])
375 if query
.has_key('AnchorItem'):
376 anchor
= query
['AnchorItem'][0]
377 anchor
= unquote(anchor
)
378 anchor
= anchor
.replace("/%s/" % query
['Container'][0].split("/")[0], "", 1)
379 anchor
= anchor
.replace("/TiVoConnect?Command=QueryContainer&Container=%s&id=" % query
['Container'][0],'',1)
380 anchor
= anchor
.replace("/TiVoConnect?Command=QueryContainer&Container=%s/" % query
['Container'][0],'',1)
382 filenames
= [x
['id'] for x
in files
]
385 index
= filenames
.index(anchor
)
387 logging
.debug('Anchor not found: %s' % anchor
)
392 if query
.has_key('AnchorOffset'):
393 index
+= int(query
['AnchorOffset'][0])
397 files
= files
[index
:index
+ count
]
400 if index
+ count
< 0:
402 files
= files
[index
+ count
:index
]
405 else: # No AnchorItem
408 files
= files
[:count
]
410 index
= count
% len(files
)
411 files
= files
[count
:]
415 def QueryContainer(self
, handler
, query
):
416 tsn
= handler
.headers
.getheader('tsn', '')
417 subcname
= query
['Container'][0]
418 cname
= subcname
.split('/')[0]
419 container
= handler
.server
.containers
[cname
]
426 if subcname
.find("/")!=-1:
427 id = id.split("/")[1]
429 videos
= self
.videos
[id]
431 path
= self
.playlist
% id
432 logging
.debug('Getting: %s' % path
)
433 feed
= xml
.dom
.minidom
.parseString(urllib
.urlopen(path
).read())
434 self
.videos
.update({id: self
.getVideos(feed
)})
435 videos
= self
.videos
[id]
438 if container
.has_key('cache') and (container
.get('cache').lower().startswith('y') or container
.get('cache').lower().startswith('t')):
439 videos
= self
.videos
[id]
442 if container
.has_key('user') and not container
.has_key('path') and not container
.has_key('playlist'):
443 userfolders
= ["uploads", "favorites", "playlists"]
445 for folder
in userfolders
:
447 path
= "http://gdata.youtube.com/feeds/api/users/%s/%s" % (container
.get('user'), folder
)
449 feed
= xml
.dom
.minidom
.parseString(urllib
.urlopen(path
).read())
452 self
.videos
.update({"%s/%s" % (subcname
,folder
): self
.getVideos(feed
)})
453 video
['total_items'] = len(self
.videos
["%s/%s" % (subcname
,folder
)])
454 video
['group'] = False
455 video
['title'] = folder
456 video
['id'] = "%s/%s" % (subcname
,folder
)
457 video
['isdir'] = True
460 if container
.has_key('path'):
461 if container
.get('path').startswith("http://gdata.youtube.com/feeds/"):
462 path
= container
.get('path')
463 elif container
.has_key("author"):
464 path
= self
.user_feed
% (container
.get('author'),container
.get('path'))
465 elif container
.has_key("user"):
466 path
= self
.user_feed
% (container
.get('user'),container
.get('path'))
468 path
= container
.get('path').replace(" ", "_")
470 if container
.has_key("category"):
471 path
= path
+ "_%s" % (container
.get("category"))
472 if container
.has_key("region"):
473 self
.standardfeedsregion
% (container
.get("region").upper(), path
)
475 path
= self
.standardfeeds
% path
476 elif container
.has_key('playlist'):
477 path
= self
.playlist
% container
.get('playlist')
479 if container
.has_key('search'):
480 path
= path
+ "%sq=%s" % (qoramp(path
), container
.get('search'))
481 if path
.startswith("?"):
482 path
= self
.videos_feed
+ path
483 if container
.has_key('q'):
484 path
= path
+ "%sq=%s" % (qoramp(path
), container
.get('q'))
485 if container
.has_key('start-index'):
486 path
= path
+ "%sstart-index=%s" % (qoramp(path
), container
.get('start-index'))
487 if container
.has_key('max'):
488 path
= path
+ "%smax-results=%s" % (qoramp(path
), container
.get('max'))
489 elif container
.has_key('max-results'):
490 path
= path
+ "%smax-results=%s" % (qoramp(path
), container
.get('max-results'))
491 if container
.has_key('safeSearch'):
492 path
= path
+ "%ssafeSearch=%s" % (qoramp(path
), container
.get('safeSearch'))
494 path
= path
+ "%ssafeSearch=%s" % (qoramp(path
), "strict")
495 if container
.has_key("time"):
496 path
= path
+ "%stime=%s" % (qoramp(path
), container
.get('time'))
497 if container
.has_key("uploader"):
498 path
= path
+ "%suploader=%s" % (qoramp(path
), container
.get('uploader'))
499 if container
.has_key("restriction"):
500 path
= path
+ "%srestriction=%s" % (qoramp(path
), container
.get('restriction'))
501 if container
.has_key("orderby"):
502 path
= path
+ "%sorderby=%s" % (qoramp(path
), container
.get('orderby'))
503 if container
.has_key("lr"):
504 path
= path
+ "%slr=%s" % (qoramp(path
), container
.get('lr'))
505 if container
.has_key("location-radius"):
506 path
= path
+ "%slocation-radius=%s" % (qoramp(path
), container
.get('location-radius'))
507 if container
.has_key("location"):
508 path
= path
+ "%slocation=%s" % (qoramp(path
), container
.get('location'))
509 if container
.has_key("format"):
510 path
= path
+ "%sformat=%s" % (qoramp(path
), container
.get('format'))
511 if container
.has_key("client"):
512 path
= path
+ "%sclient=%s" % (qoramp(path
), container
.get('client'))
513 if container
.has_key("category"):
514 path
= path
+ "%scategory=%s" % (qoramp(path
), container
.get('category'))
515 if container
.has_key('v'):
516 path
= path
+ "%sv=%s" % (qoramp(path
), container
.get('v'))
517 elif container
.has_key('version'):
518 path
= path
+ "%sv=%s" % (qoramp(path
), container
.get('version'))
520 path
= path
+ "%sv=2" % (qoramp(path
))
521 if query
.has_key('ItemCount') and query
.has_key('AnchorItem'):
522 anchor
= query
['AnchorItem'][0]
523 anchor
= unquote(anchor
)
524 anchor
= anchor
.replace("/%s/" % query
['Container'][0].split("/")[0], "", 1)
525 anchor
= anchor
.replace("/TiVoConnect?Command=QueryContainer&Container=%s&id=" % query
['Container'][0],'',1)
526 anchor
= anchor
.replace("/TiVoConnect?Command=QueryContainer&Container=%s/" % query
['Container'][0],'',1)
527 filenames
= [x
['id'] for x
in self
.videos
[id]]
529 index
= filenames
.index(anchor
)
530 if int(query
['ItemCount'][0]) > 0:
533 path
= path
+ "%sstart-index=%s&max-results=%s" % (qoramp(path
),str(index
),query
['ItemCount'][0])
535 logging
.debug('Anchor not found: %s' % anchor
)
537 elif query
.has_key('ItemCount'):
538 if int(query
['ItemCount'][0])<0:
539 urllib2
.open(path
+ "%smax-results=0" % (qoramp(path
))
540 path
= path
+ "%sstart-index=%s&max-results=%s" % (qoramp(path
), self
.maxresults
+ int(query
['ItemCount'][0]), str(abs(int(query
['ItemCount'][0]))))
542 path
= path
+ "%smax-results=%s" % (qoramp(path
),query
['ItemCount'][0])
547 logging
.debug('Getting: %s' % path
)
549 feed
= xml
.dom
.minidom
.parseString(urllib
.urlopen(path
).read())
551 total
= int(feed
.getElementsByTagName("openSearch:totalResults")[0].firstChild
.data
)
552 self
.maxresults
= total
554 nvideos
= self
.getVideos(feed
)
555 for video
in range(len(nvideos
)):
557 self
.videos
[id].insert(offset
+video
,nvideos
[video
])
559 self
.videos
.update({id: []})
560 self
.videos
[id].insert(offset
+video
,nvideos
[video
])
562 self
.videos
.update({id: nvideos
})
564 if container
.has_key("refresh"):
565 if container
.get("refresh").lower() == "yes":
567 refresh
['total_items'] = 1
568 refresh
['group'] = False
569 refresh
['title'] = "Refresh"
570 refresh
['id'] = "refresh"
571 refresh
['isdir'] = True
572 videos
.append(refresh
)
574 if container
.has_key("group"):
578 if video
[container
.get("group")] in groups
:
579 self
.videos
["%s/%s" % (subcname
,video
[container
.get("group")])].append(video
)
580 for folder
in folders
:
581 if folder
['id'] == "%s/%s" % (subcname
,video
[container
.get("group")]):
582 folder
['total_items'] = folder
['total_items'] + 1
584 self
.videos
["%s/%s" % (subcname
,video
[container
.get("group")])] = []
585 self
.videos
["%s/%s" % (subcname
,video
[container
.get("group")])].append(video
)
586 groups
.append(video
[container
.get("group")])
588 folder
['total_items'] = 1
589 folder
['group'] = False
590 folder
['title'] = video
[container
.get("group")]
591 folder
['id'] = "%s/%s" % (subcname
,video
[container
.get("group")])
592 folder
['isdir'] = True
593 folders
.append(folder
)
594 for folder
in folders
:
595 if folder
['total_items'] == 1:
596 folder
.update(self
.videos
[folder
['id']][0])
597 videos
, start
= self
.item_count(query
, folders
)
599 videos
, start
= self
.item_count(query
, nvideos
)
601 t
= Template(CONTAINER_TEMPLATE
, filter=EncodeUnicode
)
603 t
.name
= subcname
#self.getTitle(feed)
610 t
.guid
= config
.getGUID()
611 t
.tivos
= config
.tivos
612 t
.tivo_names
= config
.tivo_names
613 handler
.send_response(200)
614 handler
.send_header('Content-Type', 'text/xml')
615 handler
.end_headers()
616 handler
.wfile
.write(t
)
618 def TVBusQuery(self
, handler
, query
):
619 tsn
= handler
.headers
.getheader('tsn', '')
621 search
= "http://gdata.youtube.com/feeds/api/videos?q=%s&max-results=%s&v=2"
625 for video
in self
.videos
[query
['Container'][0]]:
626 if video
['id'] == id:
631 file_info
= self
.getVideo(xml
.dom
.minidom
.parseString(urllib
.urlopen(search
% (id,"1")).read()))
633 file_info
= self
.getVideo(xml
.dom
.minidom
.parseString(urllib
.urlopen(search
% (id,"1")).read()))
635 file_info
= self
.getVideo(xml
.dom
.minidom
.parseString(urllib
.urlopen(search
% (id,"1")).read()))
637 handler
.send_response(200)
638 handler
.end_headers()
639 t
= Template(TVBUS_TEMPLATE
, filter=EncodeUnicode
)
642 handler
.wfile
.write(t
)
644 def XSL(self
, handler
, query
):
645 handler
.send_response(200)
646 handler
.end_headers()
647 handler
.wfile
.write(XSL_TEMPLATE
)
649 def Push(self
, handler
, query
):
650 id = unquote(query
['id'][0])
652 search
= "http://gdata.youtube.com/feeds/api/videos?q=%s&max-results=%s&v=2"
654 tsn
= query
['tsn'][0]
655 for key
in handler
.tivo_names
:
656 if handler
.tivo_names
[key
] == tsn
:
662 for video
in self
.videos
[query
['Container'][0]]:
663 if video
['id'] == id:
668 file_info
= self
.getVideo(xml
.dom
.minidom
.parseString(urllib
.urlopen(search
% (id,"1")).read()))
670 file_info
= self
.getVideo(xml
.dom
.minidom
.parseString(urllib
.urlopen(search
% (id,"1")).read()))
673 s
= socket
.socket(socket
.AF_INET
, socket
.SOCK_DGRAM
)
674 s
.connect(('tivo.com',123))
675 ip
= s
.getsockname()[0]
676 container
= quote(query
['Container'][0].split('/')[0])
677 port
= config
.getPort()
679 url
= 'http://%s:%s/%s/%s' % (ip
, port
, container
, quote(id))
686 description
= file_info
['description'],
687 duration
= int(file_info
['duration']) / 1000,
688 size
= file_info
['size'],
689 title
= file_info
['title'],
690 subtitle
= file_info
['title'])
693 handler
.send_response(500)
694 handler
.end_headers()
695 handler
.wfile
.write('%s\n\n%s' % (e
, traceback
.format_exc() ))
698 referer
= handler
.headers
.getheader('Referer')
699 handler
.send_response(302)
700 handler
.send_header('Location', referer
)
701 handler
.end_headers()
703 class EncodeUnicode(Filter
):
704 def filter(self
, val
, **kw
):
705 """Encode Unicode strings, by default in UTF-8"""
707 if kw
.has_key('encoding'):
708 encoding
= kw
['encoding']
712 if type(val
) == type(u
''):
713 filtered
= val
.encode(encoding
)
718 class VideoDetails(DictMixin
):
720 def __init__(self
, d
=None):
726 def __getitem__(self
, key
):
727 if key
not in self
.d
:
728 self
.d
[key
] = self
.default(key
)
731 def __contains__(self
, key
):
734 def __setitem__(self
, key
, value
):
737 def __delitem__(self
):
744 return self
.d
.__iter
__()
747 return self
.d
.iteritems()
749 def default(self
, key
):
752 'episodeNumber' : '0',
753 'displayMajorNumber' : '0',
754 'displayMinorNumber' : '0',
755 'isEpisode' : 'true',
756 'colorCode' : ('COLOR', '4'),
757 'showType' : ('SERIES', '5'),
758 'tvRating' : ('NR', '7')
762 elif key
.startswith('v'):