class MediathekViewService(KodiService): def __init__(self): super(MediathekViewService, self).__init__() self.setTopic('Service') self.settings = Settings() self.notifier = Notifier() self.monitor = MediathekViewMonitor(self, 'instanceid') self.updater = MediathekViewUpdater(self.getNewLogger('Updater'), self.notifier, self.settings, self.monitor) def Init(self): self.info('Init (instance id: {})', self.monitor.instance_id) self.monitor.RegisterInstance() self.updater.Init(convert=True) self.settings.ResetUserActivity() def Run(self): self.info('Starting up... (instance id: {})', self.monitor.instance_id) while not self.monitor.abortRequested(): if self.settings.Reload() is True: # database configuration changed self.info( '===== Database Configuration has changed - Reloading the updater =====' ) self.updater.Reload() updateop = self.updater.GetCurrentUpdateOperation() if updateop == 1: # full update self.info('Initiating full update...') self.settings.SaveUpdateInstance(self.monitor.instance_id) self.updater.Update(True) elif updateop == 2: # differential update self.info('Initiating differential update...') self.settings.SaveUpdateInstance(self.monitor.instance_id) self.updater.Update(False) # Sleep/wait for abort for 60 seconds if self.monitor.waitForAbort(15): # Abort was requested while waiting. We should exit break self.info('Shutting down... (instance id: {})', self.monitor.instance_id) def Exit(self): self.info('Exit (instance id: {})', self.monitor.instance_id) self.updater.Exit() self.monitor.UnregisterInstance() def ReloadSettings(self): # self.info("===== RELOAD SETTINGS =====") # TODO: support online reconfiguration # currently there is a bug in Kodi: this event is only # triggered if the reconfiguration happen inside the # addon (via setSetting). If teh user changes something # via the settings page, NOTHING WILL HAPPEN! pass
class MediathekView(KodiPlugin): def __init__(self): super(MediathekView, self).__init__() self.settings = Settings() self.notifier = Notifier() self.database = Store(self.getNewLogger('Store'), self.notifier, self.settings) def show_main_menu(self): # Search self.addFolderItem(30901, {'mode': "search", 'extendedsearch': False}) # Search all self.addFolderItem(30902, {'mode': "search", 'extendedsearch': True}) # Browse livestreams self.addFolderItem(30903, {'mode': "livestreams"}) # Browse recently added self.addFolderItem(30904, {'mode': "recent", 'channel': 0}) # Browse recently added by channel self.addFolderItem(30905, {'mode': "recentchannels"}) # Browse by Initial->Show self.addFolderItem(30906, {'mode': "initial", 'channel': 0}) # Browse by Channel->Initial->Shows self.addFolderItem(30907, {'mode': "channels"}) # Database Information self.addActionItem(30908, {'mode': "action-dbinfo"}) # Manual database update if self.settings.updmode == 1 or self.settings.updmode == 2: self.addActionItem(30909, {'mode': "action-dbupdate"}) self.endOfDirectory() self._check_outdate() def show_searches(self, extendedsearch=False): self.addFolderItem(30931, { 'mode': "newsearch", 'extendedsearch': extendedsearch }) RecentSearches(self, extendedsearch).load().populate() self.endOfDirectory() def new_search(self, extendedsearch=False): settingid = 'lastsearch2' if extendedsearch is True else 'lastsearch1' headingid = 30902 if extendedsearch is True else 30901 # are we returning from playback ? search = self.addon.getSetting(settingid) if search: # restore previous search self.database.Search(search, FilmUI(self), extendedsearch) else: # enter search term (search, confirmed) = self.notifier.GetEnteredText('', headingid) if len(search) > 2 and confirmed is True: RecentSearches(self, extendedsearch).load().add(search).save() if self.database.Search(search, FilmUI(self), extendedsearch) > 0: self.addon.setSetting(settingid, search) else: # pylint: disable=line-too-long self.info( 'The following ERROR can be ignored. It is caused by the architecture of the Kodi Plugin Engine' ) self.endOfDirectory(False, cacheToDisc=True) # self.show_searches( extendedsearch ) def show_db_info(self): info = self.database.GetStatus() heading = self.language(30907) infostr = self.language({ 'NONE': 30941, 'UNINIT': 30942, 'IDLE': 30943, 'UPDATING': 30944, 'ABORTED': 30945 }.get(info['status'], 30941)) infostr = self.language(30965) % infostr totinfo = self.language(30971) % (info['tot_chn'], info['tot_shw'], info['tot_mov']) updatetype = self.language(30972 if info['fullupdate'] > 0 else 30973) if info['status'] == 'UPDATING' and info['filmupdate'] > 0: updinfo = self.language(30967) % ( updatetype, datetime.datetime.fromtimestamp( info['filmupdate']).strftime('%Y-%m-%d %H:%M:%S'), info['add_chn'], info['add_shw'], info['add_mov']) elif info['status'] == 'UPDATING': updinfo = self.language(30968) % (updatetype, info['add_chn'], info['add_shw'], info['add_mov']) elif info['lastupdate'] > 0 and info['filmupdate'] > 0: updinfo = self.language(30969) % ( updatetype, datetime.datetime.fromtimestamp( info['lastupdate']).strftime('%Y-%m-%d %H:%M:%S'), datetime.datetime.fromtimestamp( info['filmupdate']).strftime('%Y-%m-%d %H:%M:%S'), info['add_chn'], info['add_shw'], info['add_mov'], info['del_chn'], info['del_shw'], info['del_mov']) elif info['lastupdate'] > 0: updinfo = self.language(30970) % ( updatetype, datetime.datetime.fromtimestamp( info['lastupdate']).strftime('%Y-%m-%d %H:%M:%S'), info['add_chn'], info['add_shw'], info['add_mov'], info['del_chn'], info['del_shw'], info['del_mov']) else: updinfo = self.language(30966) xbmcgui.Dialog().textviewer( heading, infostr + '\n\n' + totinfo + '\n\n' + updinfo) def _check_outdate(self, maxage=172800): if self.settings.updmode != 1 and self.settings.updmode != 2: # no check with update disabled or update automatic return if self.database is None: # should never happen self.notifier.ShowOutdatedUnknown() return status = self.database.GetStatus() if status['status'] == 'NONE' or status['status'] == 'UNINIT': # should never happen self.notifier.ShowOutdatedUnknown() return elif status['status'] == 'UPDATING': # great... we are updating. nuthin to show return # lets check how old we are tsnow = int(time.time()) tsold = int(status['lastupdate']) if tsnow - tsold > maxage: self.notifier.ShowOutdatedKnown(status) def init(self): if self.database.Init(): if self.settings.HandleFirstRun(): pass self.settings.HandleUpdateOnStart() def run(self): # save last activity timestamp self.settings.ResetUserActivity() # process operation mode = self.get_arg('mode', None) if mode is None: self.show_main_menu() elif mode == 'search': extendedsearch = self.get_arg('extendedsearch', 'False') == 'True' self.show_searches(extendedsearch) elif mode == 'newsearch': self.new_search(self.get_arg('extendedsearch', 'False') == 'True') elif mode == 'research': search = self.get_arg('search', '') extendedsearch = self.get_arg('extendedsearch', 'False') == 'True' self.database.Search(search, FilmUI(self), extendedsearch) RecentSearches(self, extendedsearch).load().add(search).save() elif mode == 'delsearch': search = self.get_arg('search', '') extendedsearch = self.get_arg('extendedsearch', 'False') == 'True' RecentSearches( self, extendedsearch).load().delete(search).save().populate() self.runBuiltin('Container.Refresh') elif mode == 'livestreams': self.database.GetLiveStreams( FilmUI(self, [xbmcplugin.SORT_METHOD_LABEL])) elif mode == 'recent': channel = self.get_arg('channel', 0) self.database.GetRecents(channel, FilmUI(self)) elif mode == 'recentchannels': self.database.GetRecentChannels(ChannelUI(self, nextdir='recent')) elif mode == 'channels': self.database.GetChannels(ChannelUI(self, nextdir='shows')) elif mode == 'action-dbinfo': self.show_db_info() elif mode == 'action-dbupdate': self.settings.TriggerUpdate() self.notifier.ShowNotification(30963, 30964, time=10000) elif mode == 'initial': channel = self.get_arg('channel', 0) self.database.GetInitials(channel, InitialUI(self)) elif mode == 'shows': channel = self.get_arg('channel', 0) initial = self.get_arg('initial', None) self.database.GetShows(channel, initial, ShowUI(self)) elif mode == 'films': show = self.get_arg('show', 0) self.database.GetFilms(show, FilmUI(self)) elif mode == 'downloadmv': filmid = self.get_arg('id', 0) quality = self.get_arg('quality', 1) Downloader(self).download_movie(filmid, quality) elif mode == 'downloadep': filmid = self.get_arg('id', 0) quality = self.get_arg('quality', 1) Downloader(self).download_episode(filmid, quality) elif mode == 'playwithsrt': filmid = self.get_arg('id', 0) only_sru = self.get_arg('only_set_resolved_url', 'False') == 'True' Downloader(self).play_movie_with_subs(filmid, only_sru) # cleanup saved searches if mode is None or mode != 'search': self.addon.setSetting('lastsearch1', '') if mode is None or mode != 'searchall': self.addon.setSetting('lastsearch2', '') def exit(self): self.database.Exit()
class MediathekView(KodiPlugin): def __init__(self): super(MediathekView, self).__init__() self.settings = Settings() self.notifier = Notifier() self.db = Store(self.getNewLogger('Store'), self.notifier, self.settings) def showMainMenu(self): # Search self.addFolderItem(30901, {'mode': "search"}) # Search all self.addFolderItem(30902, {'mode': "searchall"}) # Browse livestreams self.addFolderItem(30903, {'mode': "livestreams"}) # Browse recently added self.addFolderItem(30904, {'mode': "recent", 'channel': 0}) # Browse recently added by channel self.addFolderItem(30905, {'mode': "recentchannels"}) # Browse by Initial->Show self.addFolderItem(30906, {'mode': "initial", 'channel': 0}) # Browse by Channel->Initial->Shows self.addFolderItem(30907, {'mode': "channels"}) # Database Information self.addActionItem(30908, {'mode': "action-dbinfo"}) # Manual database update if self.settings.updmode == 1 or self.settings.updmode == 2: self.addActionItem(30909, {'mode': "action-dbupdate"}) self.endOfDirectory() self._check_outdate() def showSearch(self, extendedsearch=False): settingid = 'lastsearch2' if extendedsearch is True else 'lastsearch1' headingid = 30902 if extendedsearch is True else 30901 # are we returning from playback ? searchText = self.addon.getSetting(settingid) if len(searchText) > 0: # restore previous search self.db.Search(searchText, FilmUI(self), extendedsearch) else: # enter search term searchText = self.notifier.GetEnteredText('', headingid) if len(searchText) > 2: if self.db.Search(searchText, FilmUI(self), extendedsearch) > 0: self.addon.setSetting(settingid, searchText) else: self.info( 'The following ERROR can be ignored. It is caused by the architecture of the Kodi Plugin Engine' ) self.endOfDirectory(False, cacheToDisc=True) # self.showMainMenu() def showDbInfo(self): info = self.db.GetStatus() heading = self.language(30907) infostr = self.language({ 'NONE': 30941, 'UNINIT': 30942, 'IDLE': 30943, 'UPDATING': 30944, 'ABORTED': 30945 }.get(info['status'], 30941)) infostr = self.language(30965) % infostr totinfo = self.language(30971) % (info['tot_chn'], info['tot_shw'], info['tot_mov']) updatetype = self.language(30972 if info['fullupdate'] > 0 else 30973) if info['status'] == 'UPDATING' and info['filmupdate'] > 0: updinfo = self.language(30967) % ( updatetype, datetime.datetime.fromtimestamp( info['filmupdate']).strftime('%Y-%m-%d %H:%M:%S'), info['add_chn'], info['add_shw'], info['add_mov']) elif info['status'] == 'UPDATING': updinfo = self.language(30968) % (updatetype, info['add_chn'], info['add_shw'], info['add_mov']) elif info['lastupdate'] > 0 and info['filmupdate'] > 0: updinfo = self.language(30969) % ( updatetype, datetime.datetime.fromtimestamp( info['lastupdate']).strftime('%Y-%m-%d %H:%M:%S'), datetime.datetime.fromtimestamp( info['filmupdate']).strftime('%Y-%m-%d %H:%M:%S'), info['add_chn'], info['add_shw'], info['add_mov'], info['del_chn'], info['del_shw'], info['del_mov']) elif info['lastupdate'] > 0: updinfo = self.language(30970) % ( updatetype, datetime.datetime.fromtimestamp( info['lastupdate']).strftime('%Y-%m-%d %H:%M:%S'), info['add_chn'], info['add_shw'], info['add_mov'], info['del_chn'], info['del_shw'], info['del_mov']) else: updinfo = self.language(30966) xbmcgui.Dialog().textviewer( heading, infostr + '\n\n' + totinfo + '\n\n' + updinfo) def doDownloadFilm(self, filmid, quality): if self.settings.downloadpath: film = self.db.RetrieveFilmInfo(filmid) if film is None: # film not found - should never happen return # check if the download path is reachable if not xbmcvfs.exists(self.settings.downloadpath): self.notifier.ShowError(self.language(30952), self.language(30979)) return # get the best url if quality == '0' and film.url_video_sd: videourl = film.url_video_sd elif quality == '2' and film.url_video_hd: videourl = film.url_video_hd else: videourl = film.url_video # prepare names showname = mvutils.cleanup_filename(film.show)[:64] filestem = mvutils.cleanup_filename(film.title)[:64] extension = os.path.splitext(videourl)[1] if not extension: extension = u'.mp4' if not filestem: filestem = u'Film-{}'.format(film.id) if not showname: showname = filestem # prepare download directory and determine episode number dirname = self.settings.downloadpath + showname + '/' episode = 1 if xbmcvfs.exists(dirname): ( _, epfiles, ) = xbmcvfs.listdir(dirname) for epfile in epfiles: match = re.search('^.* [eE][pP]([0-9]*)\.[^/]*$', epfile) if match and len(match.groups()) > 0: if episode <= int(match.group(1)): episode = int(match.group(1)) + 1 else: xbmcvfs.mkdir(dirname) # prepare resulting filenames fileepi = filestem + u' - EP%04d' % episode movname = dirname + fileepi + extension srtname = dirname + fileepi + u'.srt' ttmname = dirname + fileepi + u'.ttml' nfoname = dirname + fileepi + u'.nfo' # download video bgd = KodiBGDialog() bgd.Create(self.language(30974), fileepi + extension) try: bgd.Update(0) mvutils.url_retrieve_vfs(videourl, movname, bgd.UrlRetrieveHook) bgd.Close() self.notifier.ShowNotification( 30960, self.language(30976).format(videourl)) except Exception as err: bgd.Close() self.error('Failure downloading {}: {}', videourl, err) self.notifier.ShowError( 30952, self.language(30975).format(videourl, err)) # download subtitles if film.url_sub: bgd = KodiBGDialog() bgd.Create(30978, fileepi + u'.ttml') try: bgd.Update(0) mvutils.url_retrieve_vfs(film.url_sub, ttmname, bgd.UrlRetrieveHook) try: ttml2srt(xbmcvfs.File(ttmname, 'r'), xbmcvfs.File(srtname, 'w')) except Exception as err: self.info('Failed to convert to srt: {}', err) bgd.Close() except Exception as err: bgd.Close() self.error('Failure downloading {}: {}', film.url_sub, err) # create NFO Files self._make_nfo_files(film, episode, dirname, nfoname, videourl) else: self.notifier.ShowError(30952, 30958) def doEnqueueFilm(self, filmid): self.info('Enqueue {}', filmid) def _check_outdate(self, maxage=172800): if self.settings.updmode != 1 and self.settings.updmode != 2: # no check with update disabled or update automatic return if self.db is None: # should never happen self.notifier.ShowOutdatedUnknown() return status = self.db.GetStatus() if status['status'] == 'NONE' or status['status'] == 'UNINIT': # should never happen self.notifier.ShowOutdatedUnknown() return elif status['status'] == 'UPDATING': # great... we are updating. nuthin to show return # lets check how old we are tsnow = int(time.time()) tsold = int(status['lastupdate']) if tsnow - tsold > maxage: self.notifier.ShowOutdatedKnown(status) def _make_nfo_files(self, film, episode, dirname, filename, videourl): # create NFO files if not xbmcvfs.exists(dirname + 'tvshow.nfo'): try: with closing(xbmcvfs.File(dirname + 'tvshow.nfo', 'w')) as file: file.write(b'<tvshow>\n') file.write(b'<id></id>\n') file.write( bytearray('\t<title>{}</title>\n'.format(film.show), 'utf-8')) file.write( bytearray( '\t<sorttitle>{}</sorttitle>\n'.format(film.show), 'utf-8')) # TODO: file.write( bytearray( '\t<year>{}</year>\n'.format( 2018 ), 'utf-8' ) ) file.write( bytearray( '\t<studio>{}</studio>\n'.format(film.channel), 'utf-8')) file.write(b'</tvshow>\n') except Exception as err: self.error('Failure creating show NFO file for {}: {}', videourl, err) try: with closing(xbmcvfs.File(filename, 'w')) as file: file.write(b'<episodedetails>\n') file.write( bytearray('\t<title>{}</title>\n'.format(film.title), 'utf-8')) file.write(b'\t<season>1</season>\n') file.write(b'\t<autonumber>1</autonumber>\n') file.write( bytearray('\t<episode>{}</episode>\n'.format(episode), 'utf-8')) file.write( bytearray( '\t<showtitle>{}</showtitle>\n'.format(film.show), 'utf-8')) file.write( bytearray('\t<plot>{}</plot>\n'.format(film.description), 'utf-8')) file.write( bytearray('\t<aired>{}</aired>\n'.format(film.aired), 'utf-8')) if film.seconds > 60: file.write( bytearray( '\t<runtime>{}</runtime>\n'.format( int(film.seconds / 60)), 'utf-8')) file.write( bytearray('\t<studio>{}</studio\n'.format(film.channel), 'utf-8')) file.write(b'</episodedetails>\n') except Exception as err: self.error('Failure creating episode NFO file for {}: {}', videourl, err) def Init(self): self.args = urlparse.parse_qs(sys.argv[2][1:]) self.db.Init() if self.settings.HandleFirstRun(): # TODO: Implement Issue #16 pass def Do(self): # save last activity timestamp self.settings.ResetUserActivity() # process operation mode = self.args.get('mode', None) if mode is None: self.showMainMenu() elif mode[0] == 'search': self.showSearch() elif mode[0] == 'searchall': self.showSearch(extendedsearch=True) elif mode[0] == 'livestreams': self.db.GetLiveStreams(FilmUI(self, [xbmcplugin.SORT_METHOD_LABEL])) elif mode[0] == 'recent': channel = self.args.get('channel', [0]) self.db.GetRecents(channel[0], FilmUI(self)) elif mode[0] == 'recentchannels': self.db.GetRecentChannels(ChannelUI(self, nextdir='recent')) elif mode[0] == 'channels': self.db.GetChannels(ChannelUI(self, nextdir='shows')) elif mode[0] == 'action-dbinfo': self.showDbInfo() elif mode[0] == 'action-dbupdate': self.settings.TriggerUpdate() self.notifier.ShowNotification(30963, 30964, time=10000) elif mode[0] == 'initial': channel = self.args.get('channel', [0]) self.db.GetInitials(channel[0], InitialUI(self)) elif mode[0] == 'shows': channel = self.args.get('channel', [0]) initial = self.args.get('initial', [None]) self.db.GetShows(channel[0], initial[0], ShowUI(self)) elif mode[0] == 'films': show = self.args.get('show', [0]) self.db.GetFilms(show[0], FilmUI(self)) elif mode[0] == 'download': filmid = self.args.get('id', [0]) quality = self.args.get('quality', [1]) self.doDownloadFilm(filmid[0], quality[0]) elif mode[0] == 'enqueue': self.doEnqueueFilm(self.args.get('id', [0])[0]) # cleanup saved searches if mode is None or mode[0] != 'search': self.addon.setSetting('lastsearch1', '') if mode is None or mode[0] != 'searchall': self.addon.setSetting('lastsearch2', '') def Exit(self): self.db.Exit()