Make Favoritesfeeds and Playlists work again
[enigma2-plugins.git] / youtubeplayer / src / YouTubeInterface.py
1 # -*- coding: utf-8 -*-
2 ############################################################################
3 #    Copyright (C) 2008 by Volker Christian                                #
4 #    Volker.Christian@fh-hagenberg.at                                      #
5 #                                                                          #
6 #    This program is free software; you can redistribute it and#or modify  #
7 #    it under the terms of the GNU General Public License as published by  #
8 #    the Free Software Foundation; either version 2 of the License, or     #
9 #    (at your option) any later version.                                   #
10 #                                                                          #
11 #    This program is distributed in the hope that it will be useful,       #
12 #    but WITHOUT ANY WARRANTY; without even the implied warranty of        #
13 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         #
14 #    GNU General Public License for more details.                          #
15 #                                                                          #
16 #    You should have received a copy of the GNU General Public License     #
17 #    along with this program; if not, write to the                         #
18 #    Free Software Foundation, Inc.,                                       #
19 #    59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             #
20 ############################################################################
21
22 import gdata.youtube
23 import gdata.youtube.service
24
25 from gdata.service import BadAuthentication
26
27 from Tools.LoadPixmap import LoadPixmap
28
29 from twisted.web.client import downloadPage
30
31 from urllib2 import urlopen, Request, URLError, HTTPError
32 #, quote, unquote, unquote_plus
33 from urllib import quote, unquote_plus, unquote
34
35 from httplib import HTTPConnection, HTTPException
36
37 from urlparse import parse_qs
38
39 from socket import gaierror, error
40
41 import os
42 import re
43
44 # http://code.google.com/apis/youtube/reference.html#youtube_data_api_tag_media:group
45
46 std_headers = {
47         'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2',
48         'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
49         'Accept': 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
50         'Accept-Language': 'en-us,en;q=0.5',
51 }
52
53 class YouTubeUser():
54         def __init__(self, cfg):
55                 self.cfg = cfg
56
57
58         def getCfg(self):
59                 return self.cfg
60
61
62         def getName(self):
63                 return self.cfg.name.value
64
65
66         def name(self):
67                 return self.cfg.name
68
69
70         def getEmail(self):
71                 return self.cfg.email.value
72
73
74         def email(self):
75                 return self.cfg.email
76
77
78         def getPassword(self):
79                 return self.cfg.password.value
80
81
82         def password(self):
83                 return self.cfg.password
84
85
86         def login(self):
87                 return interface.login(self)
88
89
90 class YouTubeFeed():
91         def __init__(self, feed, favoritesFeed = False):
92                 print "[YTB] YouTubeFeed::__init__()"
93                 self.feed = feed
94                 self.favoritesFeed = favoritesFeed
95                 self.entries = []
96                 self.update()
97
98
99         def update(self):
100                 print "[YTB] YouTubeFeed::update()"
101                 sequenceNumber = int(self.feed.start_index.text)
102                 print self.feed.entry
103                 for entry in self.feed.entry:
104                         self.entries.append(YouTubeEntry(self, entry, sequenceNumber, self.favoritesFeed))
105                         sequenceNumber = sequenceNumber + 1
106
107
108         def getTitle(self):
109                 return self.feed.title.text
110
111
112         def getEntries(self):
113                 print "[YTB] YouTubeFeed::getEntries()"
114                 return self.entries
115
116
117         def itemCount(self):
118                 print "[YTB] YouTubeFeed::itemCount()"
119                 return self.feed.items_per_page.text
120
121
122         def getTotalResults(self):
123                 return self.feed.total_results.text
124         
125
126         def getNextFeed(self):
127                 print "[YTB] YouTubeFeed::getNextFeed()"
128                 for link in self.feed.link:
129                         if link.rel == "next":
130                                 return link.href
131                 return None
132
133
134         def getPreviousFeed(self):
135                 print "[YTB] YouTubeFeed::getPreviousFeed()"
136                 for link in self.feed.link:
137                         if link.rel == "previous":
138                                 return link.href
139                 return None
140
141
142         def getSelfFeed(self):
143                 print "[YTB] YouTubeFeed::getSelfFeed()"
144                 for link in self.feed.link:
145                         if link.rel == "self":
146                                 return link.href
147                 return None
148
149
150         def loadThumbnails(self, callback):
151                 print "[YTB] YouTubeFeed::loadThumbnails()"
152                 for entry in self.entries:
153                         entry.loadThumbnails(callback)
154
155
156 class YouTubeEntry():
157         def __init__(self, feed, entry, sequenceNumber, favoritesFeed = False):
158                 print "[YTB] YouTubeEntry::__init__()"
159                 self.feed = feed
160                 self.entry = entry
161                 self.sequenceNumber = sequenceNumber
162                 self.favoritesFeed = favoritesFeed
163                 self.thumbnail = {}
164
165
166         def isPlaylistEntry(self):
167                 return False
168
169
170         def getYouTubeId(self):
171                 print "[YTB] YouTubeEntry::getYouTubeId()"
172                 ret = None
173                 if self.entry.media.player:
174                         split = self.entry.media.player.url.split("=")
175                         ret = split.pop()
176                         if ret == 'youtube_gdata':
177                                 tmpval=split.pop()
178                                 if tmpval.endswith("&feature"):
179                                         tmp = tmpval.split("&")
180                                         ret = tmp.pop(0)
181                 return ret
182
183
184         def getTitle(self):
185                 print "[YTB] YouTubeEntry::getTitle()"
186                 return self.entry.media.title.text
187
188
189         def getDescription(self):
190                 print "[YTB] YouTubeEntry::getDescription()"
191                 return self.entry.media.description.text
192
193
194         def getThumbnailUrl(self, index):
195                 print "[YTB] YouTubeEntry::getThumbnailUrl"
196                 if index < len(self.entry.media.thumbnail):
197                         return self.entry.media.thumbnail[index].url
198                 return None
199
200
201         def getRelatedFeed(self):
202                 print "[YTB] YouTubeEntry::getRelatedFeed()"
203                 for link in self.entry.link:
204                         print "Related link: ", link.rel.endswith
205                         if link.rel.endswith("video.related"):
206                                 print "Found Related: ", link.href
207                                 return link.href
208
209
210         def getResponsesFeed(self):
211                 print "[YTB] YouTubeEntry::getResponseFeed()"
212                 for link in self.entry.link:
213                         print "Responses link: ", link.rel.endswith
214                         if link.rel.endswith("video.responses"):
215                                 print "Found Responses: ", link.href
216                                 return link.href
217
218
219         def loadThumbnail(self, index, callback):
220                 print "[YTB] YouTubeEntry::loadThumbnail()"
221                 thumbnailUrl = self.getThumbnailUrl(index)
222                 if thumbnailUrl is not None and self.getYouTubeId() is not None:
223                         thumbnailFile = "/tmp/" + self.getYouTubeId() + "_" + str(index) + ".jpg"
224                         self.thumbnail[str(index)] = None
225                         cookie = {"entry" : self, "file" : thumbnailFile, "callback" : callback, "index" : index}
226                         downloadPage(thumbnailUrl, thumbnailFile).addCallback(fetchFinished, cookie).addErrback(fetchFailed, cookie)
227                 
228
229         def loadThumbnails(self, callback):
230                 print "[YTB] YouTubeEntry::loadThumbnails()"
231                 self.loadThumbnail(0, callback)
232
233
234         def getVideoUrl(self, fmt):
235                 video_id = str(self.getYouTubeId())
236                 for el_type in ['detailpage', 'embedded', 'vevo']:
237                         video_info_url = ('http://www.youtube.com/get_video_info?&video_id=%s&el=%s&ps=default&eurl=&gl=DE&hl=en'% (video_id, el_type))
238                         request = Request(video_info_url, None, std_headers)
239                         try:
240                                 video_info_page = urlopen(request).read()
241                                 video_info = parse_qs(video_info_page)
242                                 if 'token' in video_info:
243                                         break
244                         except (URLError, HTTPException, error), err:
245                                 return None, # ('ERROR: unable to download video info webpage: %s' % str(err))
246                 if 'token' not in video_info:
247                         if 'reason' not in video_info:
248                                 reason = 'Unable to extract "t" parameter for unknown reason'
249                         else:
250                                 reason = unquote_plus(video_info['reason'][0])
251                         return None #, reason
252                 else:
253                         token = video_info['token'][0]
254                         video_real_url = 'http://www.youtube.com/get_video?video_id=%s&t=%s&eurl=&el=detailpage&ps=default&gl=US&hl=en&fmt=18' % (video_id, token)
255                         return video_real_url #, 'OK'
256
257
258         def getDuration(self):
259                 if self.entry.media is not None and self.entry.media.duration is not None:
260                         return self.entry.media.duration.seconds
261                 return "not available"
262
263         
264         def getRatingAverage(self):
265                 if self.entry.rating is not None:
266                         return self.entry.rating.average
267                 return "not available"
268
269
270         def getNumRaters(self):
271                 if self.entry.rating is not None:
272                         return self.entry.rating.num_raters
273                 return ""
274         
275         
276         def getRatingMax(self):
277                 if self.entry.rating is not None:
278                         return self.entry.rating.max
279                 return "not available"
280         
281         def getRatingMin(self):
282                 if self.entry.rating is not None:
283                         return self.entry.rating.min
284                 return "not available"
285
286         
287         def getFavoriteCount(self):
288                 if self.entry.statistics is not None:
289                         return self.entry.statistics.favorite_count
290                 return "not available"
291         
292         
293         def getViewCount(self):
294                 if self.entry.statistics is not None:
295                         return self.entry.statistics.view_count
296                 return "not available"
297
298         
299         def getAuthor(self):
300                 authorList = []
301                 for author in self.entry.author:
302                         authorList.append(author.name.text)
303                 authors = ", ".join(authorList)
304                 return authors
305
306         
307         def getPublishedOn(self):
308                 if self.entry.published is not None:
309                         return self.entry.published.text
310                 return "unknown"
311
312         
313         def getCategory(self):
314                 return self.entry.GetYouTubeCategoryAsString()
315
316         
317         def getTags(self):
318                 if self.entry.media is not None and self.entry.media.keywords is not None:
319                         return self.entry.media.keywords.text
320                 return "not available"
321
322
323         def belongsToFavorites(self):
324                 return self.favoritesFeed
325
326
327         def belongsToPlaylistId(self):
328                 return self.playlistId
329
330
331 class YouTubePlaylistFeed():
332         def __init__(self, feed):
333                 print "[YTB] YouTubePlayListFeed::__init__()"
334                 self.feed = feed
335                 self.entries = []
336                 self.update()
337
338
339         def update(self):
340                 print "[YTB] YouTubePlayListFeed::update()"
341                 for entry in self.feed.entry:
342                         self.entries.append(YouTubePlaylistEntry(entry))
343
344
345         def getTitle(self):
346                 print "[YTB] YouTubePlayListFeed::getTitle()"
347                 return self.feed.title.text
348
349
350         def getEntries(self):
351                 print "[YTB] YouTubePlayListFeed::getEntries()"
352                 return self.entries
353
354
355 class YouTubePlaylistEntry():
356         def __init__(self, entry):
357                 print "[YTB] YouTubePlaylistEntry::__init__()"
358                 self.entry = entry
359
360
361         def getTitle(self):
362                 print "[YTB] YouTubePlaylistEntry::getTitle()"
363                 return self.entry.title.text
364
365
366         def getDescription(self):
367                 print "[YTB] YouTubePlaylistEntry::getDescription()"
368                 return self.entry.description.text
369
370
371         def getFeed(self, index = 0):
372                 print "[YTB] YouTubePlaylistEntry::getFeed()"
373                 return self.entry.feed_link[index].href
374
375         
376         def getSelfFeed(self):
377                 print "[YTB] YouTubeFeed::getSelfFeed()"
378                 for link in self.entry.link:
379                         if link.rel == "self":
380                                 return link.href
381                 return None
382
383
384 class YouTubePlaylistVideoFeed(YouTubeFeed):
385         def __init__(self, feed):
386                 print "[YTB] YouTubePlaylistVideoFeed::__init__()"
387                 YouTubeFeed.__init__(self, feed)
388
389
390         def update(self):
391                 print "[YTB] YouTubePlaylistVideoFeed::update()"
392                 sequenceNumber = 1
393                 print self.feed.entry
394                 for entry in self.feed.entry:
395                         self.entries.append(YouTubePlaylistVideoEntry(self, entry, sequenceNumber))
396                         sequenceNumber = sequenceNumber + 1
397
398         def getFeed(self):
399                 print "[YTB] YouTubeFeed::getSelfFeed()"
400                 for link in self.feed.link:
401                         if link.rel == "feed":
402                                 return link.href
403                 return None
404
405
406 class YouTubePlaylistVideoEntry(YouTubeEntry):
407         def __init__(self, feed, entry, sequenceNumber):
408                 print "[YTB] YouTubePlaylistVideoEntry::__init__()"
409                 YouTubeEntry.__init__(self, feed, entry, sequenceNumber)
410
411
412         def isPlaylistEntry(self):
413                 return True
414
415
416         def getSelf(self):
417                 print "[YTB] YouTubePlaylistVideoEntry::getSelfFeed()"
418                 for link in self.entry.link:
419                         if link.rel == "self":
420                                 return link.href
421                 return None
422
423
424         YOUTUBE_DEVELOPER_TAG_SCHEME = "http://gdata.youtube.com/schemas/2007/developertags.cat"
425         def getCategory(self):
426                 for category in self.entry.media.category:
427                         if category.scheme != YouTubePlaylistVideoEntry.YOUTUBE_DEVELOPER_TAG_SCHEME:
428                                 return category.text
429                 return "not available"
430
431
432 class YouTubeInterface():
433 #       Do not change the client_id and developer_key in the login-section!
434 #       ClientId: ytapi-VolkerChristian-YouTubePlayer-pq3mrg1o-0
435 #       DeveloperKey: AI39si7t0WNyg_tvjBPdRIvBfaUA_XrTY1LNzfjLgCn8A_m92YKtWTcR_auEmI5gKGitJb4SskrjxJSmRc3yhQ4YlHTBAzPSig
436         def __init__(self):
437                 print "[YTB] YouTubeInterface::__init__()"
438
439
440         def open(self):
441                 self.ytService = gdata.youtube.service.YouTubeService()
442                 print "[YTB] YouTubeInterface::open()"
443                 self.loggedIn = False
444
445
446         def close(self):
447                 print "[YTB] YouTubeInterface::close()"
448                 del self.ytService
449                 self.loggedIn = False
450
451
452         def login(self, user):
453                 print "[YTB] YouTubeInterface::login()"
454                 ret = False
455                 if user is not None:
456                         # http://code.google.com/apis/youtube/developers_guide_python.html#ClientLogin
457                         self.ytService.email = user.getEmail()
458                         self.ytService.password = user.getPassword()
459                         self.ytService.source = 'my-example-application'
460                         self.ytService.developer_key = "AI39si7t0WNyg_tvjBPdRIvBfaUA_XrTY1LNzfjLgCn8A_m92YKtWTcR_auEmI5gKGitJb4SskrjxJSmRc3yhQ4YlHTBAzPSig"
461                         self.ytService.client_id = "ytapi-VolkerChristian-YouTubePlayer-pq3mrg1o-0"
462                         try:
463                                 self.ytService.ProgrammaticLogin()
464                         except BadAuthentication:
465                                 pass
466                         else:
467                                 self.loggedIn = True
468                                 ret = True
469                 return ret
470
471
472         def isLoggedIn(self):
473                 return self.loggedIn
474
475
476         def search(self, searchTerms, startIndex = 1, maxResults = 25,
477                                         orderby = "relevance", time = "all_time", racy = "include", 
478                                         author = "", lr = "", categories = "", sortOrder = "ascending", format = "6"):
479                 print "[YTB] YouTubeInterface::search()"
480                 query = gdata.youtube.service.YouTubeVideoQuery()
481                 query.vq = searchTerms
482                 query.orderby = orderby
483                 query.racy = racy
484                 query.sortorder = sortOrder
485                 if lr is not None:
486                         query.lr = lr
487                 if categories[0] is not None:
488                         query.categories = categories
489 #               query.time = time
490                 query.start_index = startIndex
491                 query.max_results = maxResults
492 #               query.format = format
493                 try:
494                         feed = YouTubeFeed(self.ytService.YouTubeQuery(query))
495                 except gaierror:
496                         feed = None
497                 return feed
498
499
500         def getFeed(self, url):
501                 return YouTubeFeed(self.ytService.GetYouTubeVideoFeed(url))
502
503
504         def getUserFavoritesFeed(self, userName = "default"):
505                 return YouTubeFeed(self.ytService.GetUserFavoritesFeed(userName), favoritesFeed = True)
506
507
508         def getUserPlaylistFeed(self, playlistEntry):
509                 print "[YTB] getUserPlaylistFeed: ", playlistEntry.getFeed()
510                 return YouTubePlaylistVideoFeed(self.ytService.GetYouTubePlaylistVideoFeed(playlistEntry.getFeed()))
511
512
513         def addToFavorites(self, entry):
514                 response = self.ytService.AddVideoEntryToFavorites(entry.entry)
515                 # The response, if succesfully posted is a YouTubeVideoEntry
516                 if isinstance(response, gdata.youtube.YouTubeVideoEntry):
517                         print "[YTB] Video successfully added to favorites"
518                         return response
519                 else:
520                         return None
521
522
523         def removeFromFavorites(self, entry):
524                 response = self.ytService.DeleteVideoEntryFromFavorites(entry.getYouTubeId())
525                 if response is True:
526                         print "[YTB] Video deleted from favorites"
527                 return response
528
529
530         def getPlaylistFeed(self):
531                 return YouTubePlaylistFeed(self.ytService.GetYouTubePlaylistFeed())
532
533
534         def addPlaylist(self, name, description, private):
535                 newPlaylist = None
536                 newPlaylistEntry = self.ytService.AddPlaylist(name, description, private)
537                 if isinstance(newPlaylistEntry, gdata.youtube.YouTubePlaylistEntry):
538                         newPlaylist = YouTubePlaylistEntry(newPlaylistEntry)
539                 return newPlaylist
540
541
542         def deletePlaylist(self, playlistEntry):
543                 playListUrl = playlistEntry.getSelfFeed()
544                 return self.ytService.DeletePlaylist(playListUrl)
545
546         
547         def removeFromPlaylist(self, playlistVideoEntry):
548                 print "[YTB] Removing from Playlist"
549                 response = self.ytService.Delete(playlistVideoEntry.getSelf())
550                 if response:
551                         print "[YTB] Successfull deleted"
552                 else:
553                         print "[YTB] Delete unsuccessfull"
554                 return response
555
556
557         def addToPlaylist(self, playlistEntry, videoEntry):
558                 print "[YTB] Adding to Playlist"
559                 playlistUri = playlistEntry.getFeed()
560                 response = self.ytService.AddPlaylistVideoEntryToPlaylist(
561                                                 playlistUri, videoEntry.getYouTubeId(), videoEntry.getTitle(), videoEntry.getDescription())
562                 if isinstance(response, gdata.youtube.YouTubePlaylistVideoEntry):
563                         print "[YTB] Video added"
564                         return response
565                 else:
566                         return None
567
568
569 def fetchFailed(string, cookie):
570         print "[YTB] fetchFailed(): ", string
571         if os.path.exists(cookie["file"]):
572                 os.remove(cookie["file"])
573         cookie["callback"](cookie["entry"])
574
575
576 def fetchFinished(string, cookie):
577         print "[YTB] fetchFinished(): ", string
578         if os.path.exists(cookie["file"]):
579                 print "Loading filename %s" % cookie["file"]
580                 cookie["entry"].thumbnail[str(cookie["index"])] = LoadPixmap(cookie["file"])
581                 os.remove(cookie["file"])
582         cookie["callback"](cookie["entry"])
583
584
585 interface = YouTubeInterface()