Enigma2 plugin to to play various online streams (mostly Latvian).

YouTubeUi.py 53KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531
  1. import os
  2. from twisted.web.client import downloadPage
  3. from enigma import ePicLoad, eServiceReference, eTimer, getDesktop
  4. from Components.ActionMap import ActionMap
  5. from Components.AVSwitch import AVSwitch
  6. from Components.config import config, ConfigDirectory, ConfigSelection, \
  7. ConfigSubsection, ConfigText, ConfigYesNo, getConfigListEntry
  8. from Components.ConfigList import ConfigListScreen
  9. from Components.Label import Label
  10. from Components.Pixmap import Pixmap
  11. from Components.ScrollLabel import ScrollLabel
  12. from Components.Sources.List import List
  13. from Components.Sources.StaticText import StaticText
  14. from Components.Task import job_manager
  15. from Plugins.Plugin import PluginDescriptor
  16. from Screens.ChoiceBox import ChoiceBox
  17. from Screens.InfoBar import InfoBar, MoviePlayer
  18. from Screens.MessageBox import MessageBox
  19. from Screens.Screen import Screen
  20. from Tools.BoundFunction import boundFunction
  21. from Tools.Directories import resolveFilename, SCOPE_HDD, SCOPE_PLUGINS
  22. from Tools.LoadPixmap import LoadPixmap
  23. from . import _
  24. config.plugins.YouTube = ConfigSubsection()
  25. config.plugins.YouTube.saveHistory = ConfigYesNo(default = True)
  26. config.plugins.YouTube.searchResult = ConfigSelection(
  27. [('4', '4'),
  28. ('8', '8'),
  29. ('16', '16'),
  30. ('24', '24'),
  31. ('50', '50')
  32. ], '24')
  33. config.plugins.YouTube.searchRegion = ConfigSelection(
  34. [('', _('All')),
  35. ('AU', _('Australia')),
  36. ('BR', _('Brazil')),
  37. ('CA', _('Canada')),
  38. ('CZ', _('Czech Republic')),
  39. ('FR', _('France')),
  40. ('DE', _('Germany')),
  41. ('GB', _('Great Britain')),
  42. ('NL', _('Holland')),
  43. ('HK', _('Hong Kong')),
  44. ('IN', _('India')),
  45. ('IE', _('Ireland')),
  46. ('IL', _('Israel')),
  47. ('IT', _('Italy')),
  48. ('JP', _('Japan')),
  49. ('LV', _('Latvia')),
  50. ('MX', _('Mexico')),
  51. ('NZ', _('New Zealand')),
  52. ('PL', _('Poland')),
  53. ('RU', _('Russia')),
  54. ('KR', _('South Korea')),
  55. ('ES', _('Spain')),
  56. ('SE', _('Sweden')),
  57. ('TW', _('Taiwan')),
  58. ('TH', _('Thailand')),
  59. ('US', _('United States'))
  60. ], '')
  61. config.plugins.YouTube.searchLanguage = ConfigSelection(
  62. [('', _('All')),
  63. ('au', _('Australia')),
  64. ('br', _('Brazil')),
  65. ('ca', _('Canada')),
  66. ('cz', _('Czech Republic')),
  67. ('fr', _('France')),
  68. ('de', _('Germany')),
  69. ('gb', _('Great Britain')),
  70. ('nl', _('Holland')),
  71. ('hk', _('Hong Kong')),
  72. ('in', _('India')),
  73. ('ie', _('Ireland')),
  74. ('il', _('Israel')),
  75. ('it', _('Italy')),
  76. ('jp', _('Japan')),
  77. ('lv', _('Latvia')),
  78. ('mx', _('Mexico')),
  79. ('nz', _('New Zealand')),
  80. ('pl', _('Poland')),
  81. ('ru', _('Russia')),
  82. ('kr', _('South Korea')),
  83. ('es', _('Spain')),
  84. ('se', _('Sweden')),
  85. ('tw', _('Taiwan')),
  86. ('th', _('Thailand')),
  87. ('us', _('United States'))
  88. ], '')
  89. config.plugins.YouTube.searchOrder = ConfigSelection(
  90. [('relevance', _('Relevance')),
  91. ('date', _('Created date')),
  92. ('rating', _('Rating')),
  93. ('title', _('Title')),
  94. ('viewCount', _('View count'))
  95. ], 'relevance')
  96. config.plugins.YouTube.safeSearch = ConfigSelection(default = 'moderate', choices = [
  97. ('moderate', _('Moderate')), ('none', _('No')), ('strict', _('Yes'))])
  98. config.plugins.YouTube.maxResolution = ConfigSelection(default = '22', choices = [
  99. ('38', '3072p'), ('37', '1080p'), ('22', '720p'), ('35', '480p'),
  100. ('18', '360p'), ('5', '240p'), ('17', '144p')])
  101. config.plugins.YouTube.onMovieEof = ConfigSelection(default = 'quit', choices = [
  102. ('quit', _('Return to list')), ('ask', _('Ask user')),
  103. ('playnext', _('Play next')), ('repeat', _('Repeat'))])
  104. config.plugins.YouTube.onMovieStop = ConfigSelection(default = 'ask', choices = [
  105. ('ask', _('Ask user')), ('quit', _('Return to list'))])
  106. config.plugins.YouTube.login = ConfigYesNo(default = False)
  107. config.plugins.YouTube.downloadDir = ConfigDirectory(default=resolveFilename(SCOPE_HDD))
  108. config.plugins.YouTube.searchHistory = ConfigText(default='')
  109. config.plugins.YouTube.refreshToken = ConfigText(default='')
  110. API_KEY = 'AIzaSyCyIlbb0FIwoieEZ9RTShMVkRMisu-ZX0k'
  111. YOUTUBE_API_CLIENT_ID = '411447027255-vbgs05u1o3m8mpjs2vcd04afrg60drba.apps.googleusercontent.com'
  112. YOUTUBE_API_CLIENT_SECRET = 'fYE-8T3qf4DrLPLv3NTgvjna'
  113. class YouTubePlayer(MoviePlayer):
  114. def __init__(self, session, service, current):
  115. MoviePlayer.__init__(self, session, service)
  116. self.skinName = 'MoviePlayer'
  117. self.current = current
  118. self.servicelist = InfoBar.instance and InfoBar.instance.servicelist
  119. def leavePlayer(self):
  120. if config.plugins.YouTube.onMovieStop.value == 'ask':
  121. title = _('Stop playing this movie?')
  122. list = (
  123. (_('Yes'), 'quit'),
  124. (_('Yes, but play next video'), 'playnext'),
  125. (_('Yes, but play previous video'), 'playprev'),
  126. (_('No, but play video again'), 'repeat'),
  127. (_('No'), 'continue')
  128. )
  129. self.session.openWithCallback(self.leavePlayerConfirmed,
  130. ChoiceBox, title = title, list = list)
  131. else:
  132. self.close()
  133. def leavePlayerConfirmed(self, answer):
  134. if answer and answer[1] != 'continue':
  135. self.close(answer)
  136. def doEofInternal(self, playing):
  137. self.close([None, config.plugins.YouTube.onMovieEof.value])
  138. def getPluginList(self):
  139. from Components.PluginComponent import plugins
  140. list = []
  141. for p in plugins.getPlugins(where = PluginDescriptor.WHERE_EXTENSIONSMENU):
  142. if p.name != _('YouTube'):
  143. list.append(((boundFunction(self.getPluginName, p.name),
  144. boundFunction(self.runPlugin, p), lambda: True), None))
  145. return list
  146. def openCurEventView(self):
  147. self.session.open(YouTubeInfo, current = self.current)
  148. def showSecondInfoBar(self):
  149. self.hide()
  150. self.hideTimer.stop()
  151. self.openCurEventView()
  152. def showMovies(self):
  153. pass
  154. def openServiceList(self):
  155. if hasattr(self, "toggleShow"):
  156. self.toggleShow()
  157. class YouTubeMain(Screen):
  158. screenWidth = getDesktop(0).size().width()
  159. if screenWidth and screenWidth == 1280:
  160. skin = """<screen position="center,center" size="730,514">
  161. <widget name="text" position="15,0" size="700,30" halign="center" font="Regular;24" />
  162. <widget source="list" render="Listbox" position="15,32" size="700,432" \
  163. scrollbarMode="showOnDemand" >
  164. <convert type="TemplatedMultiContent" >
  165. {"template": [
  166. MultiContentEntryPixmapAlphaTest(pos=(0,0), \
  167. size=(100,72), png=2), # Thumbnail
  168. MultiContentEntryText(pos=(110,1), size=(575,52), \
  169. font=0, flags=RT_HALIGN_LEFT|RT_VALIGN_CENTER|RT_WRAP, text=3), # Title
  170. MultiContentEntryText(pos=(120, 50), size=(200,22), \
  171. font=1, flags=RT_HALIGN_LEFT, text=4), # Views
  172. MultiContentEntryText(pos=(360,50), size=(200,22), \
  173. font=1, flags=RT_HALIGN_LEFT, text=5), # Duration
  174. ],
  175. "fonts": [gFont("Regular",20), gFont("Regular",16)],
  176. "itemHeight": 72}
  177. </convert>
  178. </widget>
  179. <widget name="info" position="50,479" size="35,25" pixmap="skin_default/buttons/key_info.png" \
  180. transparent="1" alphatest="on" />
  181. <widget name="red" position="215,467" size="140,40" pixmap="skin_default/buttons/red.png" \
  182. transparent="1" alphatest="on" />
  183. <widget name="green" position="375,467" size="140,40" pixmap="skin_default/buttons/green.png" \
  184. transparent="1" alphatest="on" />
  185. <widget source="key_red" render="Label" position="215,472" zPosition="2" size="140,30" \
  186. valign="center" halign="center" font="Regular;22" transparent="1" />
  187. <widget source="key_green" render="Label" position="375,472" zPosition="2" size="140,30" \
  188. valign="center" halign="center" font="Regular;22" transparent="1" />
  189. <widget name="menu" position="645,479" size="35,25" pixmap="skin_default/buttons/key_menu.png" \
  190. transparent="1" alphatest="on" />
  191. <widget name="thumbnail" position="0,0" size="100,72" /> # Thumbnail size in list
  192. </screen>"""
  193. elif screenWidth and screenWidth == 1920:
  194. skin = """<screen position="center,center" size="1095,771">
  195. <widget name="text" position="22,0" size="1050,45" halign="center" font="Regular;36" />
  196. <widget source="list" render="Listbox" position="22,48" size="1050,648" \
  197. scrollbarMode="showOnDemand" >
  198. <convert type="TemplatedMultiContent" >
  199. {"template": [
  200. MultiContentEntryPixmapAlphaTest(pos=(0,0), \
  201. size=(150,108), png=2), # Thumbnail
  202. MultiContentEntryText(pos=(165,1), size=(862,78), \
  203. font=0, flags=RT_HALIGN_LEFT|RT_VALIGN_CENTER|RT_WRAP, text=3), # Title
  204. MultiContentEntryText(pos=(180, 75), size=(300,33), \
  205. font=1, flags=RT_HALIGN_LEFT, text=4), # Views
  206. MultiContentEntryText(pos=(540,75), size=(300,33), \
  207. font=1, flags=RT_HALIGN_LEFT, text=5), # Duration
  208. ],
  209. "fonts": [gFont("Regular",30), gFont("Regular",24)],
  210. "itemHeight": 108}
  211. </convert>
  212. </widget>
  213. <widget name="info" position="75,718" size="53,38" pixmap="skin_default/buttons/key_info.png" \
  214. transparent="1" alphatest="on" />
  215. <widget name="red" position="322,707" size="210,60" pixmap="skin_default/buttons/red.png" \
  216. transparent="1" alphatest="on" />
  217. <widget name="green" position="563,707" size="210,60" pixmap="skin_default/buttons/green.png" \
  218. transparent="1" alphatest="on" />
  219. <widget source="key_red" render="Label" position="322,714" zPosition="2" size="210,45" \
  220. valign="center" halign="center" font="Regular;33" transparent="1" />
  221. <widget source="key_green" render="Label" position="563,714" zPosition="2" size="210,45" \
  222. valign="center" halign="center" font="Regular;33" transparent="1" />
  223. <widget name="menu" position="968,718" size="53,38" pixmap="skin_default/buttons/key_menu.png" \
  224. transparent="1" alphatest="on" />
  225. <widget name="thumbnail" position="0,0" size="150,108" /> # Thumbnail size in list
  226. </screen>"""
  227. else:
  228. skin = """<screen position="center,center" size="630,370">
  229. <widget name="text" position="15,0" size="600,30" halign="center" font="Regular;24" />
  230. <widget source="list" render="Listbox" position="15,32" size="600,288" \
  231. scrollbarMode="showOnDemand" >
  232. <convert type="TemplatedMultiContent" >
  233. {"template": [
  234. MultiContentEntryPixmapAlphaTest(pos=(0,0), \
  235. size=(100,72), png=2), # Thumbnail
  236. MultiContentEntryText(pos=(110,1), size=(475,52), \
  237. font=0, flags=RT_HALIGN_LEFT|RT_VALIGN_CENTER|RT_WRAP, text=3), # Title
  238. MultiContentEntryText(pos=(120, 50), size=(200,22), \
  239. font=1, flags=RT_HALIGN_LEFT, text=4), # Views
  240. MultiContentEntryText(pos=(360,50), size=(200,22), \
  241. font=1, flags=RT_HALIGN_RIGHT, text=5), # Duration
  242. ],
  243. "fonts": [gFont("Regular",20), gFont("Regular",16)],
  244. "itemHeight": 72}
  245. </convert>
  246. </widget>
  247. <widget name="info" position="30,335" size="35,25" pixmap="skin_default/buttons/key_info.png" \
  248. transparent="1" alphatest="on" />
  249. <widget name="red" position="114,323" size="140,40" pixmap="skin_default/buttons/red.png" \
  250. transparent="1" alphatest="on" />
  251. <widget name="green" position="374,323" size="140,40" pixmap="skin_default/buttons/green.png" \
  252. transparent="1" alphatest="on" />
  253. <widget source="key_red" render="Label" position="114,328" zPosition="2" size="140,30" \
  254. valign="center" halign="center" font="Regular;22" transparent="1" />
  255. <widget source="key_green" render="Label" position="374,328" zPosition="2" size="140,30" \
  256. valign="center" halign="center" font="Regular;22" transparent="1" />
  257. <widget name="menu" position="565,335" size="35,25" pixmap="skin_default/buttons/key_menu.png" \
  258. transparent="1" alphatest="on" />
  259. <widget name="thumbnail" position="0,0" size="100,72" /> # Thumbnail size in list
  260. </screen>"""
  261. def __init__(self, session):
  262. Screen.__init__(self, session)
  263. self.setTitle(_('YouTube'))
  264. self['info'] = Pixmap()
  265. self['info'].hide()
  266. self['red'] = Pixmap()
  267. self['red'].hide()
  268. self['green'] = Pixmap()
  269. self['green'].hide()
  270. self['menu'] = Pixmap()
  271. self['menu'].hide()
  272. self['key_red'] = StaticText('')
  273. self['key_green'] = StaticText('')
  274. self['actions'] = ActionMap(['WizardActions', 'ColorActions', 'MovieSelectionActions'],
  275. {
  276. 'back': self.cancel,
  277. 'ok': self.ok,
  278. 'red': self.cancel,
  279. 'green': self.ok,
  280. 'up': self.selectPrevious,
  281. 'down': self.selectNext,
  282. 'contextMenu': self.openMenu,
  283. 'showEventInfo': self.showEventInfo
  284. }, -2)
  285. text = _('YouTube starting. Please wait...')
  286. self['text'] = Label()
  287. self['text'].setText(text)
  288. self['list'] = List([])
  289. self['thumbnail'] = Pixmap()
  290. self['thumbnail'].hide()
  291. self.splitTaimer = eTimer()
  292. self.splitTaimer.timeout.callback.append(self.splitTaimerStop)
  293. self.thumbnailTaimer = eTimer()
  294. self.thumbnailTaimer.timeout.callback.append(self.updateThumbnails)
  295. self.picloads = {}
  296. self.thumbnails = {}
  297. self.sc = AVSwitch().getFramebufferScale()
  298. self.list = 'main'
  299. self.action = 'startup'
  300. self.value = [None, None, '']
  301. self.prevIndex = []
  302. self.prevEntryList = []
  303. self.entryList = []
  304. self.ytdl = None
  305. self.youtube = None
  306. self.nextPageToken = None
  307. self.prevPageToken = None
  308. self.isAuth = False
  309. self.activeDownloads = 0
  310. self.searchResult = config.plugins.YouTube.searchResult.value
  311. self.pageStartIndex = 1
  312. self.pageEndIndex = int(self.searchResult)
  313. self.onLayoutFinish.append(self.layoutFinish)
  314. self.onClose.append(self.cleanVariables)
  315. def layoutFinish(self):
  316. self.thumbSize = [self['thumbnail'].instance.size().width(),
  317. self['thumbnail'].instance.size().height()]
  318. defThumbnail = resolveFilename(SCOPE_PLUGINS,
  319. 'Extensions/YouTube/icons/icon.png')
  320. self.decodeThumbnail('default', defThumbnail)
  321. self.splitTaimer.start(1)
  322. def cleanVariables(self):
  323. del self.thumbnailTaimer
  324. del self.splitTaimer
  325. self.ytdl = None
  326. self.youtube = None
  327. self.thumbnails = None
  328. self.entryList = None
  329. self.prevEntryList = None
  330. def createDefEntryList(self, entry_list, append):
  331. if not append:
  332. self.entryList = []
  333. for Id, Title in entry_list:
  334. self.entryList.append((
  335. Id, # Id
  336. None, # Thumbnail url
  337. None, # Thumbnail
  338. Title, # Title
  339. '', # Views
  340. '', # Duration
  341. None, # Video url
  342. None, # Description
  343. None, # Likes
  344. None, # Dislikes
  345. None, # Big thumbnail url
  346. None, # Channel Id
  347. '', # Published
  348. ))
  349. def createMainList(self):
  350. self.list = 'main'
  351. self.value = [None, None, '']
  352. self.text = _('Choose what you want to do')
  353. self.createDefEntryList([
  354. ['Search', _('Search')],
  355. ['PubFeeds', _('Public feeds')]
  356. ], False)
  357. if config.plugins.YouTube.login.value and \
  358. config.plugins.YouTube.refreshToken.value != '':
  359. self.createDefEntryList([['MyFeeds', _('My feeds')]], True)
  360. self.setEntryList()
  361. def createSearchList(self):
  362. self.list = 'search'
  363. self.value = [None, None, '']
  364. self.text = _('Search')
  365. self.createDefEntryList([
  366. ['Searchvideo', _('Search videos')],
  367. ['Searchchannel', _('Search channels')],
  368. ['Searchplaylist', _('Search playlists')],
  369. ['Searchbroadcasts', _('Search live broadcasts')]
  370. ], False)
  371. self.setEntryList()
  372. def createFeedList(self):
  373. self.list = 'feeds'
  374. self.value = [None, None, '']
  375. self.text = _('Public feeds')
  376. self.createDefEntryList([
  377. ['top_rated', _('Top rated')],
  378. ['most_viewed', _('Most viewed')],
  379. ['most_recent', _('Recent')],
  380. ['HD_videos', _('HD videos')],
  381. ['embedded_videos', _('Embedded in webpages')],
  382. ['episodes', _('Shows')],
  383. ['movies', _('Movies')]
  384. ], False)
  385. self.setEntryList()
  386. def createMyFeedList(self):
  387. self.list = 'myfeeds'
  388. self.value = [None, None, '']
  389. self.text = _('My feeds')
  390. self.createDefEntryList([
  391. ['my_subscriptions', _('My Subscriptions')],
  392. ['my_watch_later', _('Watch Later')],
  393. ['my_history', _('History')],
  394. ['my_liked_videos', _('Liked videos')],
  395. ['my_favorites', _('Favorites')],
  396. ['my_uploads', _('Uploads')],
  397. ['my_playlists', _('Playlists')]
  398. ], False)
  399. self.setEntryList()
  400. def screenCallback(self, value = None, action = None):
  401. self.value = value
  402. self.action = action
  403. if action == 'OpenSearch':
  404. text = _('Download search results. Please wait...')
  405. elif action in ['playVideo', 'downloadVideo']:
  406. text = _('Extract video url. Please wait...')
  407. else:
  408. text = _('Download feed entries. Please wait...')
  409. self['text'].setText(text)
  410. self['list'].setList([])
  411. self['key_red'].setText('')
  412. self['key_green'].setText('')
  413. self['red'].hide()
  414. self['green'].hide()
  415. self['menu'].hide()
  416. self['info'].hide()
  417. self.splitTaimer.start(1)
  418. def splitTaimerStop(self):
  419. self.splitTaimer.stop()
  420. if self.action == 'startup':
  421. from YouTubeVideoUrl import YouTubeVideoUrl
  422. self.ytdl = YouTubeVideoUrl()
  423. self.createBuild()
  424. self.createMainList()
  425. for job in job_manager.getPendingJobs():
  426. self.activeDownloads += 1
  427. elif self.action in ['playVideo', 'downloadVideo']:
  428. videoUrl = self.value[6]
  429. if not videoUrl: # remember video url
  430. videoUrl, urlError = self.getVideoUrl()
  431. if urlError:
  432. self.session.open(MessageBox,
  433. _('There was an error in extract video url:\n%s') % urlError,
  434. MessageBox.TYPE_INFO, timeout = 8)
  435. else:
  436. count = 0
  437. for entry in self.entryList:
  438. if entry[0] == self.value[0]:
  439. self.entryList[count] = (
  440. entry[0], # Id
  441. entry[1], # Thumbnail url
  442. entry[2], # Thumbnail
  443. entry[3], # Title
  444. entry[4], # Views
  445. entry[5], # Duration
  446. videoUrl, # Video url
  447. entry[7], # Description
  448. entry[8], # Likes
  449. entry[9], # Dislikes
  450. entry[10], # Big thumbnail url
  451. entry[11], # Channel Id
  452. entry[12], # Published
  453. )
  454. break
  455. count += 1
  456. if videoUrl:
  457. if self.action == 'playVideo':
  458. service = eServiceReference(4097, 0, videoUrl)
  459. service.setName(self.value[3])
  460. current = [self.value[3], self.value[4], self.value[5], self.value[7],
  461. self.value[8], self.value[9], self.value[10], self.value[12]]
  462. print "[YouTube] Play:", videoUrl
  463. self.session.openWithCallback(self.playCallback,
  464. YouTubePlayer, service = service, current = current)
  465. else:
  466. self.videoDownload(videoUrl, self.value[3])
  467. self.setEntryList()
  468. self.setPreviousList()
  469. else:
  470. self.setEntryList()
  471. self.setPreviousList()
  472. self.value = [None, None, '']
  473. else:
  474. entryList = self.createEntryList()
  475. self.value[2] = ''
  476. if not entryList:
  477. self.session.open(MessageBox,
  478. _('There was an error in creating entry list!\nMaybe try other feeds...'),
  479. MessageBox.TYPE_INFO, timeout = 8)
  480. self.setEntryList()
  481. self.setPreviousList()
  482. self.prevEntryList.pop()
  483. else:
  484. self.entryList = entryList
  485. self.text = self.value[1]
  486. self.setEntryList()
  487. def setEntryList(self):
  488. self['text'].setText(self.text)
  489. self['list'].setList(self.entryList)
  490. self['red'].show()
  491. self['green'].show()
  492. self['menu'].show()
  493. self['key_red'].setText(_('Exit'))
  494. self['key_green'].setText(_('Open'))
  495. if self.list == 'videolist':
  496. self['info'].show()
  497. self.createThumbnails()
  498. def createThumbnails(self):
  499. for entry in self.entryList:
  500. if entry[2]: # If thumbnail created
  501. continue
  502. entryId = entry[0]
  503. if entryId in self.thumbnails:
  504. self.updateThumbnails()
  505. else:
  506. url = entry[1]
  507. if not url:
  508. image = resolveFilename(SCOPE_PLUGINS,
  509. 'Extensions/YouTube/icons/' + entryId + '.png')
  510. self.decodeThumbnail(entryId, image)
  511. else:
  512. image = os.path.join('/tmp/', str(entryId) + '.jpg')
  513. downloadPage(url, image)\
  514. .addCallback(boundFunction(self.downloadFinished, entryId))\
  515. .addErrback(boundFunction(self.downloadFailed, entryId))
  516. def downloadFinished(self, entryId, result):
  517. image = os.path.join('/tmp/', str(entryId) + '.jpg')
  518. self.decodeThumbnail(entryId, image)
  519. def downloadFailed(self, entryId, result):
  520. print "[YouTube] Thumbnail download failed, use default for", entryId
  521. self.decodeThumbnail(entryId)
  522. def decodeThumbnail(self, entryId, image = None):
  523. if not image or not os.path.exists(image):
  524. print "[YouTube] Thumbnail not exists, use default for", entryId
  525. self.thumbnails[entryId] = True
  526. self.thumbnailTaimer.start(1)
  527. else:
  528. self.picloads[entryId] = ePicLoad()
  529. self.picloads[entryId].PictureData.get()\
  530. .append(boundFunction(self.FinishDecode, entryId, image))
  531. self.picloads[entryId].setPara((
  532. self.thumbSize[0], self.thumbSize[1],
  533. self.sc[0], self.sc[1], False, 1, '#00000000'))
  534. self.picloads[entryId].startDecode(image)
  535. def FinishDecode(self, entryId, image, picInfo = None):
  536. ptr = self.picloads[entryId].getData()
  537. if ptr:
  538. self.thumbnails[entryId] = ptr
  539. del self.picloads[entryId]
  540. if image[:4] == '/tmp':
  541. os.remove(image)
  542. self.thumbnailTaimer.start(1)
  543. def updateThumbnails(self):
  544. self.thumbnailTaimer.stop()
  545. if len(self.picloads) != 0:
  546. self.thumbnailTaimer.start(1)
  547. else:
  548. count = 0
  549. for entry in self.entryList:
  550. if not entry[2] and entry[0] in self.thumbnails:
  551. thumbnail = self.thumbnails[entry[0]]
  552. if thumbnail is True:
  553. thumbnail = self.thumbnails['default']
  554. self.entryList[count] = (
  555. entry[0], # Id
  556. entry[1], # Thumbnail url
  557. thumbnail, # Thumbnail
  558. entry[3], # Title
  559. entry[4], # Views
  560. entry[5], # Duration
  561. entry[6], # Video url
  562. entry[7], # Description
  563. entry[8], # Likes
  564. entry[9], # Dislikes
  565. entry[10], # Big thumbnail url
  566. entry[11], # Channel Id
  567. entry[12], # Published
  568. )
  569. count += 1
  570. self['list'].updateList(self.entryList)
  571. def selectNext(self):
  572. if self['list'].index + 1 < len(self.entryList): # not last enrty in entry list
  573. self['list'].selectNext()
  574. else:
  575. if self.nextPageToken: # call next serch results if it exist
  576. self.pageStartIndex = self.pageEndIndex + 1
  577. self.pageEndIndex += int(self.searchResult)
  578. self.setNextEntries()
  579. else:
  580. self['list'].setIndex(0)
  581. def selectPrevious(self):
  582. if self['list'].index > 0: # not first enrty in entry list
  583. self['list'].selectPrevious()
  584. else:
  585. if self.prevPageToken: # call previous serch results if it exist
  586. self.pageEndIndex = self.pageStartIndex - 1
  587. self.pageStartIndex -= int(self.searchResult)
  588. self.setPrevEntries()
  589. else:
  590. self['list'].setIndex(len(self.entryList) - 1)
  591. def playCallback(self, action=None):
  592. self.setEntryList()
  593. self.setPreviousList()
  594. if action:
  595. action = action[1]
  596. if action == 'quit':
  597. pass
  598. elif action == 'repeat':
  599. self.ok()
  600. elif action == 'ask':
  601. self.rememberCurList()
  602. title = _('What do you want to do?')
  603. list = (
  604. (_('Quit'), 'quit'),
  605. (_('Play next video'), 'playnext'),
  606. (_('Play previous video'), 'playprev'),
  607. (_('Play video again'), 'repeat')
  608. )
  609. self.session.openWithCallback(self.playCallback,
  610. ChoiceBox, title = title, list = list)
  611. else:
  612. if action == 'playnext':
  613. self.selectNext()
  614. elif action == 'playprev':
  615. self.selectPrevious()
  616. self.playTaimer = eTimer()
  617. self.playTaimer.timeout.callback.append(self.playTaimerStop)
  618. self.playTaimer.start(1)
  619. def playTaimerStop(self):
  620. self.playTaimer.stop()
  621. del self.playTaimer
  622. self.ok()
  623. def setPreviousList(self):
  624. lastInex = self.prevIndex[len(self.prevIndex) - 1]
  625. self['list'].setIndex(lastInex[0])
  626. self.list = lastInex[1]
  627. self.text = lastInex[2]
  628. self.nextPageToken = lastInex[3]
  629. self.prevPageToken = lastInex[4]
  630. self['text'].setText(self.text)
  631. self.prevIndex.pop()
  632. def rememberCurList(self):
  633. self.prevIndex.append([self['list'].index,
  634. self.list, self.text, self.nextPageToken, self.prevPageToken])
  635. def ok(self):
  636. current = self['list'].getCurrent()
  637. if current and current[0]:
  638. print "[YouTube] Selected:", current[0]
  639. self.rememberCurList()
  640. if self.list == 'videolist':
  641. self.screenCallback(current, 'playVideo')
  642. else:
  643. self.prevEntryList.append(self.entryList)
  644. if current[0] == 'Search':
  645. self.createSearchList()
  646. elif current[0] == 'PubFeeds':
  647. self.createFeedList()
  648. elif current[0] == 'MyFeeds':
  649. self.createMyFeedList()
  650. elif self.list == 'search':
  651. from YouTubeSearch import YouTubeSearch
  652. self.session.openWithCallback(self.searchScreenCallback, YouTubeSearch)
  653. elif self.list == 'feeds':
  654. self.screenCallback([current[0], current[3], self.value[2]], 'OpenFeeds')
  655. elif self.list == 'myfeeds':
  656. self.screenCallback([current[0], current[3], self.value[2]], 'OpenMyFeeds')
  657. elif self.list == 'playlist':
  658. self.screenCallback([current[0], current[3], self.value[2]], 'OpenPlayList')
  659. elif self.list == 'channel':
  660. self.screenCallback([current[0], current[3], self.value[2]], 'OpenChannelList')
  661. def searchScreenCallback(self, searchValue = None):
  662. if not searchValue: # cancel in search
  663. self.cancel()
  664. else:
  665. self.searchResult = config.plugins.YouTube.searchResult.value
  666. self.screenCallback([self['list'].getCurrent()[0][6:], searchValue, ''], 'OpenSearch')
  667. def getVideoUrl(self):
  668. try:
  669. videoUrl = self.ytdl.extract(self.value[0])
  670. except Exception as e:
  671. print '[YouTube] Error in extract info:', e
  672. return None, e
  673. if videoUrl:
  674. return videoUrl, None
  675. print '[YouTube] Video url not found'
  676. return None, 'Video url not found!'
  677. def convertDate(self, duration):
  678. time = ':' + duration.replace('P', '')\
  679. .replace('W', '-').replace('D', ' ').replace('T', '')\
  680. .replace('H', ':').replace('M', ':').replace('S', '')
  681. if 'S' not in duration:
  682. time += '00'
  683. elif time[-2] == ':':
  684. time = time[:-1] + '0' + time[-1]
  685. if 'M' not in duration:
  686. time = time[:-2] + '00' + time[-3:]
  687. elif time[-5] == ':':
  688. time = time[:-4] + '0' + time[-4:]
  689. return time[1:]
  690. def createBuild(self):
  691. refreshToken = config.plugins.YouTube.refreshToken.value
  692. if not self.youtube or (not self.isAuth and
  693. refreshToken and config.plugins.YouTube.login.value):
  694. from YouTubeApi import YouTubeApi
  695. self.youtube = YouTubeApi(
  696. client_id = YOUTUBE_API_CLIENT_ID,
  697. client_secret = YOUTUBE_API_CLIENT_SECRET,
  698. developer_key = API_KEY,
  699. refresh_token = refreshToken)
  700. if self.youtube.access_token:
  701. self.isAuth = True
  702. else:
  703. self.isAuth = False
  704. def createEntryList(self):
  705. self.createBuild()
  706. order = 'date'
  707. searchType = 'video'
  708. q = ''
  709. videoEmbeddable = videoDefinition = videoType = eventType = ''
  710. if self.action == 'OpenSearch':
  711. order = config.plugins.YouTube.searchOrder.value
  712. if self.value[0] == 'broadcasts':
  713. eventType = 'live'
  714. else:
  715. searchType = self.value[0]
  716. if ' (' in self.value[1]:
  717. self.value[1] = self.value[1].rsplit(' (', 1)[0]
  718. q = self.value[1]
  719. elif self.action == 'OpenFeeds':
  720. if self.value[0] == 'top_rated':
  721. order = 'rating'
  722. elif self.value[0] == 'most_viewed':
  723. order = 'viewCount'
  724. elif self.value[0] == 'HD_videos':
  725. videoDefinition = 'high'
  726. elif self.value[0] == 'embedded_videos':
  727. videoEmbeddable = 'true'
  728. elif self.value[0] == 'episodes':
  729. videoType = 'episode'
  730. elif self.value[0] == 'movies':
  731. videoType = 'movie'
  732. elif self.action == 'OpenMyFeeds':
  733. if not self.isAuth:
  734. return None
  735. elif self.value[0] == 'my_watch_later':
  736. playlist = 'watchLater'
  737. elif self.value[0] == 'my_history':
  738. playlist = 'watchHistory'
  739. elif self.value[0] == 'my_liked_videos':
  740. playlist = 'likes'
  741. elif self.value[0] == 'my_favorites':
  742. playlist = 'favorites'
  743. elif self.value[0] == 'my_uploads':
  744. playlist = 'uploads'
  745. videos = []
  746. if self.action == 'OpenMyFeeds':
  747. if self.value[0] == 'my_subscriptions':
  748. self.list = 'playlist'
  749. searchResponse = self.youtube.subscriptions_list(
  750. maxResults = self.searchResult,
  751. pageToken = self.value[2]
  752. )
  753. self.nextPageToken = searchResponse.get('nextPageToken')
  754. self.prevPageToken = searchResponse.get('prevPageToken')
  755. self.setSearchResults(searchResponse.get('pageInfo', {}).get('totalResults', 0))
  756. for result in searchResponse.get('items', []):
  757. try:
  758. Id = 'UU' + result['snippet']['resourceId']['channelId'][2:]
  759. except:
  760. Id = None
  761. try:
  762. Thumbnail = str(result['snippet']['thumbnails']['high']['url'])
  763. except:
  764. Thumbnail = None
  765. try:
  766. Title = str(result['snippet']['title'])
  767. except:
  768. Title = ''
  769. try:
  770. Subscription = result['id']
  771. except:
  772. Subscription = ''
  773. videos.append((Id, Thumbnail, None, Title, '', '', Subscription,
  774. None, None, None, None, None, ''))
  775. if len(videos) > 1:
  776. videos.insert(0, ('recent_subscr', None, None, _('Recent'), '', '',
  777. None, None, None, None, None, None, ''))
  778. return videos
  779. elif self.value[0] == 'my_playlists':
  780. self.list = 'playlist'
  781. searchResponse = self.youtube.playlists_list(
  782. maxResults = self.searchResult,
  783. pageToken = self.value[2]
  784. )
  785. self.nextPageToken = searchResponse.get('nextPageToken')
  786. self.prevPageToken = searchResponse.get('prevPageToken')
  787. self.setSearchResults(searchResponse.get('pageInfo', {}).get('totalResults', 0))
  788. for result in searchResponse.get('items', []):
  789. try:
  790. Id = result['id']
  791. except:
  792. Id = None
  793. try:
  794. Thumbnail = str(result['snippet']['thumbnails']['default']['url'])
  795. except:
  796. Thumbnail = None
  797. try:
  798. Title = str(result['snippet']['title'])
  799. except:
  800. Title = ''
  801. videos.append((Id, Thumbnail, None, Title, '', '', None,
  802. None, None, None, None, None, ''))
  803. return videos
  804. else: # all other my data
  805. channel = ''
  806. searchResponse = self.youtube.channels_list(
  807. maxResults = self.searchResult,
  808. pageToken = self.value[2]
  809. )
  810. self.nextPageToken = searchResponse.get('nextPageToken')
  811. self.prevPageToken = searchResponse.get('prevPageToken')
  812. self.setSearchResults(searchResponse.get('pageInfo', {}).get('totalResults', 0))
  813. for result in searchResponse.get('items', []):
  814. channel = result['contentDetails']['relatedPlaylists'][playlist]
  815. videos = self.videoIdFromPlaylist(channel)
  816. return self.extractVideoIdList(videos)
  817. elif self.action == 'OpenPlayList':
  818. if self.value[0] == 'recent_subscr':
  819. for subscription in self.entryList:
  820. if subscription[0] != 'recent_subscr':
  821. videos += self.videoIdFromPlaylist(subscription[0])
  822. if videos:
  823. videos = sorted(self.extractVideoIdList(videos), key=lambda k: k[12], reverse=True) # sort by date
  824. del videos[int(self.searchResult):] # leaves only searchResult long list
  825. self.nextPageToken = ''
  826. self.prevPageToken = ''
  827. self.setSearchResults(int(self.searchResult))
  828. return videos
  829. else:
  830. videos = self.videoIdFromPlaylist(self.value[0])
  831. if not videos: # if channel list from subscription
  832. searchResponse = self.youtube.search_list(
  833. part = 'id,snippet',
  834. channelId = 'UC' + self.value[0][2:],
  835. maxResults = self.searchResult,
  836. pageToken = self.value[2]
  837. )
  838. return self.createList(searchResponse, True)
  839. return self.extractVideoIdList(videos)
  840. elif self.action == 'OpenChannelList':
  841. videos = self.videoIdFromChannellist(self.value[0])
  842. return self.extractVideoIdList(videos)
  843. else: # search or pub feeds
  844. searchResponse = self.youtube.search_list_full(
  845. videoEmbeddable = videoEmbeddable,
  846. safeSearch = config.plugins.YouTube.safeSearch.value,
  847. eventType = eventType,
  848. videoType = videoType,
  849. videoDefinition = videoDefinition,
  850. order = order,
  851. part = 'id,snippet',
  852. q = q,
  853. relevanceLanguage = config.plugins.YouTube.searchLanguage.value,
  854. s_type = searchType,
  855. regionCode = config.plugins.YouTube.searchRegion.value,
  856. maxResults = self.searchResult,
  857. pageToken = self.value[2]
  858. )
  859. if searchType != 'video':
  860. videos = self.createList(searchResponse, False)
  861. return videos
  862. self.nextPageToken = searchResponse.get('nextPageToken')
  863. self.prevPageToken = searchResponse.get('prevPageToken')
  864. self.setSearchResults(searchResponse.get('pageInfo', {}).get('totalResults', 0))
  865. for result in searchResponse.get('items', []):
  866. videos.append(result['id']['videoId'])
  867. return self.extractVideoIdList(videos)
  868. def extractVideoIdList(self, videos):
  869. if len(videos) == 0:
  870. return None
  871. self.list = 'videolist'
  872. searchResponse = self.youtube.videos_list(v_id=','.join(videos))
  873. videos = []
  874. for result in searchResponse.get('items', []):
  875. try:
  876. Id = result['id']
  877. except:
  878. Id = None
  879. try:
  880. Thumbnail = str(result['snippet']['thumbnails']['default']['url'])
  881. except:
  882. Thumbnail = None
  883. try:
  884. Title = str(result['snippet']['title'])
  885. except:
  886. Title = ''
  887. try:
  888. Views = str(result['statistics']['viewCount']) + _(' views')
  889. except:
  890. Views = ''
  891. try:
  892. Duration = _('Duration: ') + self.convertDate(str(result['contentDetails']['duration']))
  893. except:
  894. Duration = ''
  895. try:
  896. Description = str(result['snippet']['description'])
  897. except:
  898. Description = ''
  899. try:
  900. Likes = str(result['statistics']['likeCount']) + _(' likes')
  901. except:
  902. Likes = ''
  903. try:
  904. Dislikes = str(result['statistics']['dislikeCount']) + _(' dislikes')
  905. except:
  906. Dislikes = ''
  907. try:
  908. ThumbnailUrl = str(result['snippet']['thumbnails']['medium']['url'])
  909. except:
  910. ThumbnailUrl = None
  911. try:
  912. ChannelId = result['snippet']['channelId']
  913. except:
  914. ChannelId = None
  915. try:
  916. PublishedAt = _('Published at: ') + str(result['snippet']['publishedAt'])\
  917. .replace('T', ' ').split('.')[0]
  918. except:
  919. PublishedAt = ''
  920. try:
  921. liveBroadcast = result['snippet']['liveBroadcastContent']
  922. except:
  923. liveBroadcast = None
  924. if liveBroadcast == 'live': # if live broadcast insert in top of list
  925. videos.insert(0, (Id, Thumbnail, None, Title, Views, Duration, None,
  926. Description, Likes, Dislikes, ThumbnailUrl, ChannelId, PublishedAt))
  927. else:
  928. videos.append((Id, Thumbnail, None, Title, Views, Duration, None,
  929. Description, Likes, Dislikes, ThumbnailUrl, ChannelId, PublishedAt))
  930. return videos
  931. def videoIdFromPlaylist(self, channel):
  932. videos = []
  933. searchResponse = self.youtube.playlistItems_list(
  934. maxResults = self.searchResult,
  935. playlistId = channel,
  936. pageToken = self.value[2]
  937. )
  938. self.nextPageToken = searchResponse.get('nextPageToken')
  939. self.prevPageToken = searchResponse.get('prevPageToken')
  940. self.setSearchResults(searchResponse.get('pageInfo', {}).get('totalResults', 0))
  941. for result in searchResponse.get('items', []):
  942. try:
  943. videos.append(result['snippet']['resourceId']['videoId'])
  944. except:
  945. pass
  946. return videos
  947. def videoIdFromChannellist(self, channel):
  948. videos = []
  949. searchResponse = self.youtube.search_list(
  950. part = 'id',
  951. channelId = channel,
  952. maxResults = self.searchResult,
  953. pageToken = self.value[2]
  954. )
  955. self.nextPageToken = searchResponse.get('nextPageToken')
  956. self.prevPageToken = searchResponse.get('prevPageToken')
  957. self.setSearchResults(searchResponse.get('pageInfo', {}).get('totalResults', 0))
  958. for result in searchResponse.get('items', []):
  959. try:
  960. videos.append(result['id']['videoId'])
  961. except:
  962. pass
  963. return videos
  964. def createList(self, searchResponse, subscription):
  965. videos = []
  966. self.nextPageToken = searchResponse.get('nextPageToken')
  967. self.prevPageToken = searchResponse.get('prevPageToken')
  968. self.setSearchResults(searchResponse.get('pageInfo', {}).get('totalResults', 0))
  969. kind = self.list
  970. for result in searchResponse.get('items', []):
  971. try:
  972. kind = result['id']['kind'].split('#')[1]
  973. except:
  974. kind = self.list
  975. try:
  976. Id = result['id'][kind + 'Id']
  977. except:
  978. Id = None
  979. try:
  980. Thumbnail = str(result['snippet']['thumbnails']['default']['url'])
  981. except:
  982. Thumbnail = None
  983. try:
  984. Title = str(result['snippet']['title'])
  985. except:
  986. Title = ''
  987. videos.append((Id, Thumbnail, None, Title, '', '', None,
  988. None, None, None, None, None, ''))
  989. if subscription and len(videos) > 1:
  990. videos.insert(0, ('recent_subscr', None, None, _('Recent'), '', '',
  991. None, None, None, None, None, None, ''))
  992. self.list = kind
  993. return videos
  994. def setSearchResults(self, totalResults):
  995. if not self.prevPageToken:
  996. self.pageStartIndex = 1
  997. self.pageEndIndex = int(self.searchResult)
  998. if totalResults > 0:
  999. if self.pageEndIndex > totalResults:
  1000. self.pageEndIndex = totalResults
  1001. if ' (' in self.value[1]:
  1002. self.value[1] = self.value[1].rsplit(' (', 1)[0]
  1003. self.value[1] = self.value[1][:40] + _(' (%d-%d of %d)') % \
  1004. (self.pageStartIndex, self.pageEndIndex, totalResults)
  1005. def cancel(self):
  1006. entryListIndex = len(self.prevEntryList) - 1
  1007. if len(self.prevIndex) == 0 or entryListIndex < 0:
  1008. self.close()
  1009. else:
  1010. self.entryList = self.prevEntryList[entryListIndex]
  1011. self.prevEntryList.pop()
  1012. self.setEntryList()
  1013. self.setPreviousList()
  1014. self['info'].hide()
  1015. def openMenu(self):
  1016. if self.list == 'main':
  1017. self.session.openWithCallback(self.configScreenCallback, YouTubeSetup)
  1018. else:
  1019. title = _('What do you want to do?')
  1020. clist = ((_('YouTube setup'), 'setup'),)
  1021. if self.nextPageToken:
  1022. clist += ((_('Next %s entries') % self.searchResult,
  1023. 'next'),)
  1024. if self.prevPageToken:
  1025. clist += ((_('Previous %s entries') % self.searchResult,
  1026. 'prev'),)
  1027. if self.isAuth:
  1028. if self.list == 'videolist':
  1029. clist += ((_('Rate video'), 'rate'),
  1030. (_('Subscribe this video channel'), 'subscribe_video'),)
  1031. if self.value[0] == 'my_favorites':
  1032. clist += ((_('Remove from favorites'), 'rem_favorite'),)
  1033. else:
  1034. clist += ((_('Add to favorites'), 'add_favorite'),)
  1035. elif self.list == 'channel' and self.prevIndex[1][1] != 'myfeeds':
  1036. clist += ((_('Subscribe'), 'subscribe'),)
  1037. elif self.list == 'playlist' and self.prevIndex[1][1] == 'myfeeds' and \
  1038. len(self.prevIndex) == 2:
  1039. clist += ((_('Unsubscribe'), 'unsubscribe'),)
  1040. if self.list == 'videolist':
  1041. clist += ((_('Search'), 'search'),
  1042. (_('Download video'), 'download'),)
  1043. if self.activeDownloads > 0:
  1044. clist += ((_('Active video downloads'), 'download_list'),)
  1045. clist += ((_('Close YouTube'), 'close'),)
  1046. self.session.openWithCallback(self.menuCallback,
  1047. ChoiceBox, title = title, list = clist)
  1048. def menuCallback(self, answer):
  1049. if answer:
  1050. msg = None
  1051. clist = None
  1052. if answer[1] == 'setup':
  1053. self.session.openWithCallback(self.configScreenCallback, YouTubeSetup)
  1054. elif answer[1] == 'next':
  1055. self.setNextEntries()
  1056. elif answer[1] == 'prev':
  1057. self.setPrevEntries()
  1058. elif answer[1] == 'rate':
  1059. clist = ((_('I like this'), 'like'),
  1060. (_('I dislike this'), 'dislike'),
  1061. (_('Remove my rating'), 'none'),)
  1062. elif answer[1] == 'subscribe':
  1063. current = self['list'].getCurrent()[0]
  1064. msg = self.subscribeChannel(current)
  1065. elif answer[1] == 'subscribe_video':
  1066. current = self['list'].getCurrent()[11]
  1067. msg = self.subscribeChannel(current)
  1068. elif answer[1] == 'rem_favorite':
  1069. current = self['list'].getCurrent()[0]
  1070. msg = self.removeFromFavorites(current)
  1071. elif answer[1] == 'add_favorite':
  1072. current = self['list'].getCurrent()[0]
  1073. msg = self.addToFavorites(current)
  1074. elif answer[1] == 'unsubscribe':
  1075. msg = self.unsubscribeChannel()
  1076. elif answer[1] == 'search':
  1077. clist = ((_('Search for similar'), 'similar'),
  1078. (_('Videos from this video channel'), 'channel_videos'),)
  1079. elif answer[1] == 'similar':
  1080. term = self['list'].getCurrent()[3][:40]
  1081. self.screenCallback(['video', term, ''], 'OpenSearch')
  1082. elif answer[1] == 'channel_videos':
  1083. current = self['list'].getCurrent()
  1084. self.screenCallback([current[11], current[3][:40], ''],
  1085. 'OpenChannelList')
  1086. elif answer[1] == 'download':
  1087. current = self['list'].getCurrent()
  1088. if current[6]:
  1089. self.videoDownload(current[6], current[3])
  1090. else:
  1091. self.rememberCurList()
  1092. self.screenCallback(current, 'downloadVideo')
  1093. elif answer[1] == 'download_list':
  1094. from YouTubeDownload import YouTubeDownloadList
  1095. self.session.open(YouTubeDownloadList)
  1096. elif answer[1] == 'close':
  1097. self.close()
  1098. else:
  1099. msg = self.rateVideo(answer[1])
  1100. if msg:
  1101. self.session.open(MessageBox, msg, MessageBox.TYPE_INFO, timeout = 3)
  1102. elif clist:
  1103. title = _('What do you want to do?')
  1104. self.session.openWithCallback(self.menuCallback,
  1105. ChoiceBox, title = title, list = clist)
  1106. def configScreenCallback(self, callback=None):
  1107. self.searchResult = config.plugins.YouTube.searchResult.value
  1108. if self.list == 'main': # maybe autentification changed
  1109. self.createMainList()
  1110. def subscribeChannel(self, channelId):
  1111. if self.youtube.subscriptions_insert(channelId = channelId):
  1112. return _('Subscribed!')
  1113. return _('There was an error!')
  1114. def unsubscribeChannel(self):
  1115. subscribtionId = self['list'].getCurrent()[6]
  1116. if subscribtionId and self.youtube.subscriptions_delete(subscribtionId):
  1117. # update subscriptions list
  1118. newEntryList = []
  1119. for entry in self.entryList:
  1120. if entry[6] != subscribtionId:
  1121. newEntryList.append(entry)
  1122. self.entryList = newEntryList
  1123. self['list'].updateList(self.entryList)
  1124. return _('Unsubscribed!')
  1125. return _('There was an error!')
  1126. def myFavoritesId(self):
  1127. playlistId = ''
  1128. searchResponse = self.youtube.playlists_list(
  1129. maxResults = '50',
  1130. pageToken = ''
  1131. )
  1132. for result in searchResponse.get('items', []):
  1133. try:
  1134. if result['snippet']['title'] == 'Favorites':
  1135. playlistId = str(result['id'])
  1136. break
  1137. except:
  1138. pass
  1139. return playlistId
  1140. def removeFromFavorites(self, videoId):
  1141. tmpId = self.myFavoritesId()
  1142. if tmpId:
  1143. searchResponse = self.youtube.playlistItems_list(
  1144. maxResults = '50',
  1145. playlistId = tmpId,
  1146. pageToken = ''
  1147. )
  1148. tmpId = None
  1149. for result in searchResponse.get('items', []):
  1150. try:
  1151. if videoId == result['snippet']['resourceId']['videoId']:
  1152. tmpId = result['id']
  1153. break
  1154. except:
  1155. pass
  1156. if tmpId and self.youtube.playlistItems_delete(videoId = tmpId):
  1157. # update favorites list
  1158. newEntryList = []
  1159. for entry in self.entryList:
  1160. if entry[0] != videoId:
  1161. newEntryList.append(entry)
  1162. self.entryList = newEntryList
  1163. self['list'].updateList(self.entryList)
  1164. return _('Removed!')
  1165. return _('There was an error!')
  1166. def addToFavorites(self, videoId):
  1167. playlistId = self.myFavoritesId()
  1168. if playlistId and self.youtube.playlistItems_insert(
  1169. playlistId = playlistId,
  1170. videoId = videoId
  1171. ):
  1172. return _('Added!')
  1173. return _('There was an error!')
  1174. def rateVideo(self, rating):
  1175. videoId = self['list'].getCurrent()[0]
  1176. if self.youtube.videos_rate(videoId = videoId, rating = rating):
  1177. text = {
  1178. 'like': _('Liked!'),
  1179. 'dislike': _('Disliked!'),
  1180. 'none': _('Rating removed!')
  1181. }
  1182. return text[rating]
  1183. else:
  1184. return _('There was an error!')
  1185. def showEventInfo(self):
  1186. if self.list == 'videolist':
  1187. current = self['list'].getCurrent()
  1188. current = [current[3], current[4], current[5], current[7],
  1189. current[8], current[9], current[10], current[12]]
  1190. self.session.open(YouTubeInfo, current = current)
  1191. def videoDownload(self, url, title):
  1192. downloadDir = config.plugins.YouTube.downloadDir.value
  1193. if downloadDir[0] == "'":
  1194. downloadDir = downloadDir[2:-2]
  1195. if not os.path.exists(downloadDir):
  1196. msg = _('Sorry, download directory not exist!\nPlease specify in the settings existing directory.')
  1197. else:
  1198. outputfile = os.path.join(downloadDir, title.replace('/', '') + '.mp4')
  1199. if os.path.exists(outputfile):
  1200. msg = _('Sorry, this file already exists:\n%s') % title
  1201. else:
  1202. from YouTubeDownload import downloadJob
  1203. job_manager.AddJob(downloadJob(url, outputfile, title[:20], self.downloadStop))
  1204. self.activeDownloads += 1
  1205. msg = _('Video download started!')
  1206. self.session.open(MessageBox, msg, MessageBox.TYPE_INFO, timeout = 5)
  1207. def downloadStop(self):
  1208. self.activeDownloads -= 1
  1209. def setPrevEntries(self):
  1210. self.value[2] = self.prevPageToken
  1211. self.usePageToken()
  1212. def setNextEntries(self):
  1213. self.value[2] = self.nextPageToken
  1214. self.usePageToken()
  1215. def usePageToken(self):
  1216. text = self.text
  1217. self.cancel()
  1218. if self.list == 'search':
  1219. self.rememberCurList()
  1220. self.prevEntryList.append(self.entryList)
  1221. self.screenCallback([self['list'].getCurrent()[0][6:],
  1222. text, self.value[2]], 'OpenSearch')
  1223. else:
  1224. self.ok()
  1225. class YouTubeInfo(Screen):
  1226. screenWidth = getDesktop(0).size().width()
  1227. if screenWidth and screenWidth == 1280:
  1228. skin = """<screen position="center,center" size="730,424">
  1229. <widget name="title" position="15,0" size="700,60" halign="center" font="Regular;24" />
  1230. <widget name="pic" position="20,70" size="320,180" transparent="1" alphatest="on" />
  1231. <widget name="description" position="360,70" size="360,300" font="Regular;16" />
  1232. <widget name="views" position="30,270" size="150,20" font="Regular;16" />
  1233. <widget name="duration" position="200,270" size="150,20" font="Regular;16" />
  1234. <widget name="likes" position="30,300" size="150,20" font="Regular;16" />
  1235. <widget name="dislikes" position="200,300" size="150,20" font="Regular;16" />
  1236. <widget name="published" position="30,330" size="300,20" font="Regular;16" />
  1237. <ePixmap position="295,377" size="140,40" pixmap="skin_default/buttons/red.png" \
  1238. transparent="1" alphatest="on" />
  1239. <widget source="key_red" render="Label" position="center,382" zPosition="2" size="140,30" \
  1240. valign="295" halign="center" font="Regular;22" transparent="1" />
  1241. </screen>"""
  1242. elif screenWidth and screenWidth == 1920:
  1243. skin = """<screen position="center,center" size="1095,636">
  1244. <widget name="title" position="22,0" size="1050,90" halign="center" font="Regular;36" />
  1245. <widget name="pic" position="30,105" size="320,180" transparent="1" alphatest="on" />
  1246. <widget name="description" position="380,105" size="670,453" font="Regular;24" />
  1247. <widget name="views" position="45,305" size="225,30" font="Regular;24" />
  1248. <widget name="duration" position="45,355" size="225,30" font="Regular;24" />
  1249. <widget name="likes" position="45,405" size="225,30" font="Regular;24" />
  1250. <widget name="dislikes" position="45,455" size="225,30" font="Regular;24" />
  1251. <widget name="published" position="45,505" size="335,30" font="Regular;24" />
  1252. <ePixmap position="442,565" size="210,60" pixmap="skin_default/buttons/red.png" \
  1253. transparent="1" alphatest="on" />
  1254. <widget source="key_red" render="Label" position="442,563" zPosition="2" size="210,60" \
  1255. valign="center" halign="center" font="Regular;33" transparent="1" />
  1256. </screen>"""
  1257. else:
  1258. skin = """<screen position="center,center" size="630,370">
  1259. <widget name="title" position="15,0" size="600,60" halign="center" font="Regular;24" />
  1260. <widget name="pic" position="20,70" size="320,180" transparent="1" alphatest="on" />
  1261. <widget name="description" position="360,70" size="260,225" font="Regular;16" />
  1262. <widget name="views" position="30,270" size="150,20" font="Regular;16" />
  1263. <widget name="duration" position="200,270" size="150,20" font="Regular;16" />
  1264. <widget name="likes" position="30,300" size="150,20" font="Regular;16" />
  1265. <widget name="dislikes" position="200,300" size="150,20" font="Regular;16" />
  1266. <widget name="published" position="360,300" size="260,20" font="Regular;16" />
  1267. <ePixmap position="245,323" size="140,40" pixmap="skin_default/buttons/red.png" \
  1268. transparent="1" alphatest="on" />
  1269. <widget source="key_red" render="Label" position="245,328" zPosition="2" size="140,30" \
  1270. valign="center" halign="center" font="Regular;22" transparent="1" />
  1271. </screen>"""
  1272. def __init__(self, session, current):
  1273. Screen.__init__(self, session)
  1274. self.setTitle(_('YouTube info'))
  1275. self['key_red'] = StaticText(_('Exit'))
  1276. self['title'] = Label(current[0])
  1277. self['pic'] = Pixmap()
  1278. self['description'] = ScrollLabel(current[3])
  1279. self['views'] = Label(current[1])
  1280. self['duration'] = Label(current[2])
  1281. self['likes'] = Label(current[4])
  1282. self['dislikes'] = Label(current[5])
  1283. self['published'] = Label(current[7])
  1284. self['actions'] = ActionMap(['ColorActions',
  1285. 'InfobarShowHideActions', 'DirectionActions'],
  1286. {
  1287. 'red': self.close,
  1288. 'toggleShow': self.close,
  1289. 'hide': self.close,
  1290. 'infoButton': self.close,
  1291. 'up': self['description'].pageUp,
  1292. 'down': self['description'].pageDown
  1293. }, -2)
  1294. self.picloads = None
  1295. self.ThumbnailUrl = current[6]
  1296. self.onLayoutFinish.append(self.LayoutFinish)
  1297. def LayoutFinish(self):
  1298. if self.ThumbnailUrl:
  1299. downloadPage(self.ThumbnailUrl, '/tmp/hqdefault.jpg')\
  1300. .addCallback(self.downloadFinished)
  1301. def downloadFinished(self, result):
  1302. image = '/tmp/hqdefault.jpg'
  1303. if os.path.exists(image):
  1304. sc = AVSwitch().getFramebufferScale()
  1305. self.picloads = ePicLoad()
  1306. self.picloads.PictureData.get().append(self.FinishDecode)
  1307. self.picloads.setPara((
  1308. self['pic'].instance.size().width(),
  1309. self['pic'].instance.size().height(),
  1310. sc[0], sc[1], False, 1, '#00000000'))
  1311. self.picloads.startDecode(image)
  1312. def FinishDecode(self, picInfo = None):
  1313. ptr = self.picloads.getData()
  1314. if ptr:
  1315. self["pic"].instance.setPixmap(ptr.__deref__())
  1316. del self.picloads
  1317. os.remove('/tmp/hqdefault.jpg')
  1318. class YouTubeSetup(ConfigListScreen, Screen):
  1319. def __init__(self, session):
  1320. Screen.__init__(self, session)
  1321. self.session = session
  1322. self.skinName = ['YouTubeSetup', 'Setup']
  1323. self['key_red'] = StaticText(_('Cancel'))
  1324. self['key_green'] = StaticText(_('Ok'))
  1325. self['description'] = Label('')
  1326. self['setupActions'] = ActionMap(['SetupActions', 'ColorActions'],
  1327. {
  1328. 'cancel': self.keyCancel,
  1329. 'red': self.keyCancel,
  1330. 'ok': self.ok,
  1331. 'green': self.ok
  1332. }, -2)
  1333. self.mbox = None
  1334. self.login = config.plugins.YouTube.login.value
  1335. configlist = []
  1336. ConfigListScreen.__init__(self, configlist, session=session,
  1337. on_change = self.checkLoginSatus)
  1338. configlist.append(getConfigListEntry(_('Save search result:'),
  1339. config.plugins.YouTube.saveHistory,
  1340. _('Save your search result in the history, when search completed.')))
  1341. configlist.append(getConfigListEntry(_('Search results:'),
  1342. config.plugins.YouTube.searchResult,
  1343. _('How many search results will be returned.\nIf greater value then longer time will be needed for thumbnail download.')))
  1344. configlist.append(getConfigListEntry(_('Search region:'),
  1345. config.plugins.YouTube.searchRegion,
  1346. _('Return search results for the specified country.')))
  1347. configlist.append(getConfigListEntry(_('Search language:'),
  1348. config.plugins.YouTube.searchLanguage,
  1349. _('Return search results that are most relevant to the specified language.')))
  1350. configlist.append(getConfigListEntry(_('Sort search results by:'),
  1351. config.plugins.YouTube.searchOrder,
  1352. _('Order in which search results will be displayed.')))
  1353. configlist.append(getConfigListEntry(_('Exclude restricted content:'),
  1354. config.plugins.YouTube.safeSearch,
  1355. _('Try to exclude all restricted content from the search result.')))
  1356. configlist.append(getConfigListEntry(_('Maximum video resolution:'),
  1357. config.plugins.YouTube.maxResolution,
  1358. _('What maximum resolution used when playing video, if available.\nIf you have a slow Internet connection, you can use a lower resolution.')))
  1359. configlist.append(getConfigListEntry(_('When video ends:'),
  1360. config.plugins.YouTube.onMovieEof,
  1361. _('What to do when the video ends.')))
  1362. configlist.append(getConfigListEntry(_('When playback stop:'),
  1363. config.plugins.YouTube.onMovieStop,
  1364. _('What to do when stop playback in videoplayer.')))
  1365. configlist.append(getConfigListEntry(_('Login on startup:'),
  1366. config.plugins.YouTube.login,
  1367. _('Log in to your YouTube account when plugin starts.\nThis needs to approve in the Google home page!')))
  1368. configlist.append(getConfigListEntry(_('Download directory:'),
  1369. config.plugins.YouTube.downloadDir,
  1370. _('Specify the directory where save downloaded video files.')))
  1371. self['config'].list = configlist
  1372. self['config'].l.setList(configlist)
  1373. self.onLayoutFinish.append(self.layoutFinished)
  1374. def layoutFinished(self):
  1375. self.setTitle(_('YouTube setup'))
  1376. def ok(self):
  1377. if self["config"].getCurrent()[1] == config.plugins.YouTube.downloadDir:
  1378. from YouTubeDownload import YouTubeDirBrowser
  1379. downloadDir = config.plugins.YouTube.downloadDir.value
  1380. if downloadDir[0] == "'":
  1381. downloadDir = downloadDir[2:-2]
  1382. self.session.openWithCallback(self.downloadPath,
  1383. YouTubeDirBrowser, downloadDir)
  1384. else:
  1385. self.keySave()
  1386. def downloadPath(self, res):
  1387. if res:
  1388. config.plugins.YouTube.downloadDir.setValue(res)
  1389. def checkLoginSatus(self):
  1390. if self.login != config.plugins.YouTube.login.value:
  1391. self.login = config.plugins.YouTube.login.value
  1392. if self.login:
  1393. self.startAutentification()
  1394. def startAutentification(self):
  1395. if config.plugins.YouTube.refreshToken.value != '':
  1396. self.session.openWithCallback(self.startupCallback,
  1397. MessageBox, _('You already authorized access for this plugin to your YouTube account.\nDo you want to do it again to update access data?'))
  1398. else:
  1399. self.startupCallback(True)
  1400. def startupCallback(self, answer):
  1401. if answer:
  1402. self.session.openWithCallback(self.warningCallback,
  1403. MessageBox, _('To perform authentication will need in a web browser open Google home page, and enter the code!\nDo you currently have Internet access on the other device and we can continue?'))
  1404. def warningCallback(self, answer):
  1405. if not answer:
  1406. self.login = config.plugins.YouTube.login.value = False
  1407. else:
  1408. from OAuth import OAuth
  1409. self.splitTaimer = eTimer()
  1410. self.splitTaimer.timeout.callback.append(self.splitTaimerStop)
  1411. self.oauth = OAuth(YOUTUBE_API_CLIENT_ID, YOUTUBE_API_CLIENT_SECRET)
  1412. userCode = str(self.oauth.get_user_code())
  1413. msg = _('Go to %s\nAnd enter the code %s') % (str(self.oauth.verification_url), userCode)
  1414. print "[YouTube] ", msg
  1415. self.mbox = self.session.open(MessageBox, msg, MessageBox.TYPE_INFO)
  1416. self.splitTaimer.start(9000)
  1417. def splitTaimerStop(self):
  1418. self.splitTaimer.stop()
  1419. # Here we waiting until the user enter a code
  1420. refreshToken, retryInterval = self.oauth.get_new_token()
  1421. if not refreshToken:
  1422. self.splitTaimer.start(retryInterval * 1000)
  1423. else:
  1424. print "[YouTube] Get refresh token"
  1425. if self.mbox:
  1426. self.mbox.close()
  1427. config.plugins.YouTube.refreshToken.value = refreshToken
  1428. config.plugins.YouTube.refreshToken.save()
  1429. del self.splitTaimer
  1430. self.mbox = None
  1431. self.oauth = None