Initial Commit
[Projects.git] / pytivo-youtube / source / plugins / youtube / youtube.py
blob0a4602b9365c3917e0a365516e1dba9bc4cd80bf
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
9 import config
10 import time
11 import subprocess
12 import mind
13 import logging
14 import shutil
15 import xml
16 import urllib
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()
33 # XXX BIG HACK
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")
45 if mswindows:
46 patchSubprocess()
48 def kill(pid):
49 if mswindows:
50 win32kill(pid)
51 else:
52 import signal
53 os.kill(pid, signal.SIGTERM)
55 def win32kill(pid):
56 import ctypes
57 handle = ctypes.windll.kernel32.OpenProcess(1, False, pid)
58 ctypes.windll.kernel32.TerminateProcess(handle, -1)
59 ctypes.windll.kernel32.CloseHandle(handle)
61 def qoramp(path):
62 if path.find("?") == -1:
63 return "?"
64 else:
65 return "&"
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"
78 videos = {}
80 #def __init__(self):
81 # try:
82 # from multiprocessing import Process, Lock
83 # p = Process(target=self.LoadVideos)
84 # p.start()
85 # p.join()
86 # except(ImportError):
87 # return self
88 # return self
90 def __init__(self):
91 #logging.debug('Starting Youtube Plugin Cache')
92 #self.QueryContainer()
93 return
95 #def LoadVideos(self):
96 # while 1:
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")
109 return
111 def between(data, From, To):
112 start = data.find(From)
113 if start == -1:
114 return ''
115 start += len(From)
116 end = data.find(To, start)
117 if end == -1:
118 return ''
119 return data[start:end]
121 page = urllib.urlopen(self.watch_video + "?v=%s" % handler.path.split("/")[2]).read()
122 ticket = between(page, '"t": "', '"')
123 if not ticket:
124 handler.send_error(404)
125 return None
127 url = self.get_video + "?video_id=%s&t=%s" % (handler.path.split("/")[2], ticket)
129 print container
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 -'
139 if mswindows:
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)
145 else:
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)
152 try:
153 shutil.copyfileobj(ffmpeg.stdout, handler.wfile)
154 except:
155 kill(ffmpeg.pid)
157 source.close()
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):
166 video = {}
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:
176 try:
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
191 else:
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')
210 else:
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'] = []
233 video['vHost'] = []
234 video['vWriter'] = []
235 video['vProducer'] = []
236 video['showType'] = ('SERIES', '5')
237 video['tvRating'] = "5"
238 if video['rating']:
239 video['starRating'] = str(round(float(video['rating']) * 7 / 5)).split(".")[0]
240 else:
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)
249 try:
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
256 except(ImportError):
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
267 hours = min / 60
268 min = min % 60
269 video['iso_duration'] = 'P' + str(duration_delta.days) + \
270 'DT' + str(hours) + 'H' + str(min) + \
271 'M' + str(sec) + 'S'
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'] = []
307 video['vHost'] = []
308 video['vWriter'] = []
309 video['vProducer'] = []
310 video['showType'] = ('SERIES', '5')
311 video['tvRating'] = "5"
312 if video['rating']:
313 video['starRating'] = str(round(float(video['rating']) * 7 / 5)).split(".")[0]
314 else:
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)
323 try:
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
330 except(ImportError):
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
341 hours = min / 60
342 min = min % 60
343 video['iso_duration'] = 'P' + str(duration_delta.days) + \
344 'DT' + str(hours) + 'H' + str(min) + \
345 'M' + str(sec) + 'S'
346 video['size'] = int(video['duration']) * 546720
347 video['milliseconds'] = int(video['duration']) * 1000
348 else:
349 logging.debug('The entry named %s probably does not have proper pyTiVo Youtube Plugin support' % video['title'])
350 return False
352 return video
354 def getVideos(self, feed, offset=0):
355 videos = []
356 for entry in feed.getElementsByTagName("entry"):
357 video = self.getVideo(entry)
358 if video: videos.append(video)
359 return videos
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)
370 index = 0
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]
384 try:
385 index = filenames.index(anchor)
386 except ValueError:
387 logging.debug('Anchor not found: %s' % anchor)
389 if count > 0:
390 index += 1
392 if query.has_key('AnchorOffset'):
393 index += int(query['AnchorOffset'][0])
395 #foward count
396 if count >= 0:
397 files = files[index:index + count]
398 #backwards count
399 else:
400 if index + count < 0:
401 count = -index
402 files = files[index + count:index]
403 index += count
405 else: # No AnchorItem
407 if count >= 0:
408 files = files[:count]
409 else:
410 index = count % len(files)
411 files = files[count:]
413 return files, index
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]
421 try:
422 id = query['id'][0]
423 except(KeyError):
424 id = subcname
426 if subcname.find("/")!=-1:
427 id = id.split("/")[1]
428 try:
429 videos = self.videos[id]
430 except(KeyError):
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]
436 total = len(videos)
437 else:
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]
440 else:
441 path = ""
442 if container.has_key('user') and not container.has_key('path') and not container.has_key('playlist'):
443 userfolders = ["uploads", "favorites", "playlists"]
444 videos = []
445 for folder in userfolders:
446 video = {}
447 path = "http://gdata.youtube.com/feeds/api/users/%s/%s" % (container.get('user'), folder)
448 try:
449 feed = xml.dom.minidom.parseString(urllib.urlopen(path).read())
450 except:
451 continue
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
458 videos.append(video)
459 else:
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'))
467 else:
468 path = container.get('path').replace(" ", "_")
469 path = path.lower()
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)
474 else:
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'))
493 else:
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'))
519 else:
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]]
528 try:
529 index = filenames.index(anchor)
530 if int(query['ItemCount'][0]) > 0:
531 index += 1
532 offset=index
533 path = path + "%sstart-index=%s&max-results=%s" % (qoramp(path),str(index),query['ItemCount'][0])
534 except ValueError:
535 logging.debug('Anchor not found: %s' % anchor)
536 offset=0
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]))))
541 else:
542 path = path + "%smax-results=%s" % (qoramp(path),query['ItemCount'][0])
543 offset=0
544 else:
545 offset=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)):
556 try:
557 self.videos[id].insert(offset+video,nvideos[video])
558 except(KeyError):
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":
566 refresh = {}
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"):
575 folders = []
576 groups = []
577 for video in videos:
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
583 else:
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")])
587 folder = {}
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)
598 else:
599 videos, start = self.item_count(query, nvideos)
601 t = Template(CONTAINER_TEMPLATE, filter=EncodeUnicode)
602 t.container = cname
603 t.name = subcname #self.getTitle(feed)
604 t.total = total
605 t.start = start
606 t.videos = videos
607 t.quote = quote
608 t.escape = escape
609 t.crc = zlib.crc32
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', '')
620 id = query['id'][0]
621 search = "http://gdata.youtube.com/feeds/api/videos?q=%s&max-results=%s&v=2"
623 try:
624 found=False
625 for video in self.videos[query['Container'][0]]:
626 if video['id'] == id:
627 file_info = video
628 found=True
629 break
630 if found:
631 file_info = self.getVideo(xml.dom.minidom.parseString(urllib.urlopen(search % (id,"1")).read()))
632 else:
633 file_info = self.getVideo(xml.dom.minidom.parseString(urllib.urlopen(search % (id,"1")).read()))
634 except(KeyError):
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)
640 t.video = file_info
641 t.escape = escape
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:
657 tsn = key
658 break
660 try:
661 found = False
662 for video in self.videos[query['Container'][0]]:
663 if video['id'] == id:
664 file_info = video
665 found = True
666 break
667 if not found:
668 file_info = self.getVideo(xml.dom.minidom.parseString(urllib.urlopen(search % (id,"1")).read()))
669 except(KeyError):
670 file_info = self.getVideo(xml.dom.minidom.parseString(urllib.urlopen(search % (id,"1")).read()))
672 import socket
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))
681 try:
682 m = mind.getMind()
683 m.pushVideo(
684 tsn = tsn,
685 url = url,
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'])
691 except Exception, e:
692 import traceback
693 handler.send_response(500)
694 handler.end_headers()
695 handler.wfile.write('%s\n\n%s' % (e, traceback.format_exc() ))
696 raise
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']
709 else:
710 encoding='utf8'
712 if type(val) == type(u''):
713 filtered = val.encode(encoding)
714 else:
715 filtered = str(val)
716 return filtered
718 class VideoDetails(DictMixin):
720 def __init__(self, d=None):
721 if d:
722 self.d = d
723 else:
724 self.d = {}
726 def __getitem__(self, key):
727 if key not in self.d:
728 self.d[key] = self.default(key)
729 return self.d[key]
731 def __contains__(self, key):
732 return True
734 def __setitem__(self, key, value):
735 self.d[key] = value
737 def __delitem__(self):
738 del self.d[key]
740 def keys(self):
741 return self.d.keys()
743 def __iter__(self):
744 return self.d.__iter__()
746 def iteritems(self):
747 return self.d.iteritems()
749 def default(self, key):
750 defaults = {
751 'showingBits' : '0',
752 'episodeNumber' : '0',
753 'displayMajorNumber' : '0',
754 'displayMinorNumber' : '0',
755 'isEpisode' : 'true',
756 'colorCode' : ('COLOR', '4'),
757 'showType' : ('SERIES', '5'),
758 'tvRating' : ('NR', '7')
760 if key in defaults:
761 return defaults[key]
762 elif key.startswith('v'):
763 return []
764 else:
765 return ''