def setup_manual_server(self): ''' Setup manual servers ''' client = self.get_client() client.set_credentials(get_credentials()) manager = client.auth try: self.manual_server(manager) except RuntimeError: return credentials = client.get_credentials() save_credentials(credentials) window('emby.restart.bool', True)
def __enter__(self): ''' Do everything we need before the sync ''' LOG.info("-->[ fullsync ]") if not settings('dbSyncScreensaver.bool'): xbmc.executebuiltin('InhibitIdleShutdown(true)') self.screensaver = get_screensaver() set_screensaver(value="") self.running = True window('jellyfin_sync.bool', True) return self
def reset(): ''' Reset both the jellyfin database and the kodi database. ''' from views import Views views = Views() if not dialog("yesno", "{jellyfin}", translate(33074)): return window('jellyfin_should_stop.bool', True) count = 10 while window('jellyfin_sync.bool'): LOG.info("Sync is running...") count -= 1 if not count: dialog("ok", "{jellyfin}", translate(33085)) return if xbmc.Monitor().waitForAbort(1): return reset_kodi() reset_jellyfin() views.delete_playlists() views.delete_nodes() if dialog("yesno", "{jellyfin}", translate(33086)): reset_artwork() if dialog("yesno", "{jellyfin}", translate(33087)): xbmcvfs.delete(os.path.join(ADDON_DATA, "settings.xml")) xbmcvfs.delete(os.path.join(ADDON_DATA, "data.json")) LOG.info("[ reset settings ]") if xbmcvfs.exists(os.path.join(ADDON_DATA, "sync.json")): xbmcvfs.delete(os.path.join(ADDON_DATA, "sync.json")) settings('enableMusic.bool', False) settings('MinimumSetup', "") settings('MusicRescan.bool', False) settings('SyncInstallRunDone.bool', False) dialog("ok", "{jellyfin}", translate(33088)) xbmc.executebuiltin('RestartApp')
def service(self): ''' Keeps the service monitor going. Exit on Kodi shutdown or profile switch. if profile switch happens more than once, Threads depending on abortRequest will not trigger. ''' self.monitor = monitor.Monitor() player = self.monitor.player self.connect = connect.Connect() self.start_default() self.settings['mode'] = settings('useDirectPaths') while self.running: if window('emby_online.bool'): if self.settings['profile'] != window('emby_kodiProfile'): LOG.info("[ profile switch ] %s", self.settings['profile']) break if player.isPlaying() and player.is_playing_file( player.get_playing_file()): difference = datetime.today( ) - self.settings['last_progress'] if difference.seconds > 10: self.settings['last_progress'] = datetime.today() update = (datetime.today() - self.settings['last_progress_report'] ).seconds > 250 event('ReportProgressRequested', {'Report': update}) if update: self.settings[ 'last_progress_report'] = datetime.today() if window('emby.restart.bool'): window('emby.restart', clear=True) raise Exception('RestartService') if self.waitForAbort(1): break self.shutdown()
def get_user(self, client): ''' Save user info. ''' self.user = client['api'].get_user() self.config = client['api'].get_system_info() settings('username', self.user['Name']) settings('SeasonSpecials.bool', self.config.get('DisplaySpecialsWithinSeasons', True)) if 'PrimaryImageTag' in self.user: window( 'EmbyUserImage', api.API(self.user, client['auth/server-address']).get_user_artwork( self.user['Id']))
def run(self): with self.lock: with self.database as kodidb: with Database('emby') as embydb: while True: try: item = self.queue.get(timeout=1) except Queue.Empty: break obj = MEDIA[item['Type']](self.args[0], embydb, kodidb, self.args[1])['Remove'] try: obj(item['Id']) except LibraryException as error: if error.status == 'StopCalled': break except Exception as error: LOG.exception(error) self.queue.task_done() if window('emby_should_stop.bool'): break LOG.info("--<[ q:removed/%s ]", id(self)) self.is_done = True
def run(self): with self.lock, self.database as kodidb, Database('jellyfin') as jellyfindb: while True: try: item = self.queue.get(timeout=1) except Queue.Empty: break if item['Type'] == 'Movie': obj = Movies(self.args[0], jellyfindb, kodidb, self.args[1]).remove elif item['Type'] in ['Series', 'Season', 'Episode']: obj = TVShows(self.args[0], jellyfindb, kodidb, self.args[1]).remove elif item['Type'] in ['MusicAlbum', 'MusicArtist', 'AlbumArtist', 'Audio']: obj = Music(self.args[0], jellyfindb, kodidb, self.args[1]).remove elif item['Type'] == 'MusicVideo': obj = MusicVideos(self.args[0], jellyfindb, kodidb, self.args[1]).remove try: obj(item['Id']) except LibraryException as error: if error.status == 'StopCalled': break except Exception as error: LOG.exception(error) self.queue.task_done() if window('jellyfin_should_stop.bool'): break LOG.info("--<[ q:removed/%s ]", id(self)) self.is_done = True
def run(self): with Database('jellyfin') as jellyfindb: database = jellyfin_db.JellyfinDatabase(jellyfindb.cursor) while True: try: item_id = self.queue.get(timeout=1) except Queue.Empty: break try: media = database.get_media_by_id(item_id) self.output[media].put({'Id': item_id, 'Type': media}) except Exception as error: LOG.exception(error) items = database.get_media_by_parent_id(item_id) if not items: LOG.info("Could not find media %s in the jellyfin database.", item_id) else: for item in items: self.output[item[1]].put({'Id': item[0], 'Type': item[1]}) self.queue.task_done() if window('jellyfin_should_stop.bool'): break LOG.info("--<[ q:sort/%s ]", id(self)) self.is_done = True
def report_playback(self, report=True): ''' Report playback progress to emby server. Check if the user seek. ''' current_file = self.get_playing_file() if not self.is_playing_file(current_file): return item = self.get_file_info(current_file) if window('emby.external.bool'): return if not report: previous = item['CurrentPosition'] item['CurrentPosition'] = int(self.getTime()) if int(item['CurrentPosition']) == 1: return try: played = float(item['CurrentPosition'] * 10000000) / int( item['Runtime']) * 100 except ZeroDivisionError: # Runtime is 0. played = 0 if played > 2.0 and not self.up_next: self.up_next = True self.next_up() if (item['CurrentPosition'] - previous) < 30: return result = JSONRPC('Application.GetProperties').execute( {'properties': ["volume", "muted"]}) result = result.get('result', {}) item['Volume'] = result.get('volume') item['Muted'] = result.get('muted') item['CurrentPosition'] = int(self.getTime()) self.detect_audio_subs(item) data = { 'QueueableMediaTypes': "Video,Audio", 'CanSeek': True, 'ItemId': item['Id'], 'MediaSourceId': item['MediaSourceId'], 'PlayMethod': item['PlayMethod'], 'VolumeLevel': item['Volume'], 'PositionTicks': int(item['CurrentPosition'] * 10000000), 'IsPaused': item['Paused'], 'IsMuted': item['Muted'], 'PlaySessionId': item['PlaySessionId'], 'AudioStreamIndex': item['AudioStreamIndex'], 'SubtitleStreamIndex': item['SubtitleStreamIndex'] } item['Server']['api'].session_progress(data)
def run(self): with self.lock: with self.database as kodidb: with Database('emby') as embydb: while True: try: item = self.queue.get(timeout=1) except Queue.Empty: break obj = MEDIA[item['Type']](self.args[0], embydb, kodidb, self.args[1])[item['Type']] try: if obj(item) and self.notify: self.notify_output.put( (item['Type'], api.API(item).get_naming())) except LibraryException as error: if error.status == 'StopCalled': break except Exception as error: LOG.exception(error) self.queue.task_done() if window('emby_should_stop.bool'): break LOG.info("--<[ q:updated/%s ]", id(self)) self.is_done = True
def run(self): while True: try: item = self.queue.get(timeout=3) except Queue.Empty: break time = self.music_time if item[0] == 'Audio' else self.video_time if time and ( not self.player.isPlayingVideo() or xbmc.getCondVisibility('VideoPlayer.Content(livetv)')): dialog("notification", heading="%s %s" % (_(33049), item[0]), message=item[1], icon="{emby}", time=time, sound=False) self.queue.task_done() if window('emby_should_stop.bool'): break LOG.info("--<[ q:notify/%s ]", id(self)) self.is_done = True
def add_user(): ''' Add or remove users from the default server session. ''' if not window('jellyfin_online.bool'): return session = TheVoid('GetSession', {}).get() users = TheVoid('GetUsers', {'IsDisabled': False, 'IsHidden': False}).get() current = session[0]['AdditionalUsers'] result = dialog("select", translate(33061), [translate(33062), translate(33063)] if current else [translate(33062)]) if result < 0: return if not result: # Add user eligible = [x for x in users if x['Id'] not in [current_user['UserId'] for current_user in current]] resp = dialog("select", translate(33064), [x['Name'] for x in eligible]) if resp < 0: return user = eligible[resp] event('AddUser', {'Id': user['Id'], 'Add': True}) else: # Remove user resp = dialog("select", translate(33064), [x['UserName'] for x in current]) if resp < 0: return user = current[resp] event('AddUser', {'Id': user['UserId'], 'Add': False})
def run(self): with self.lock, self.database as kodidb, Database('jellyfin') as jellyfindb: while True: try: item = self.queue.get(timeout=1) except Queue.Empty: break obj = MEDIA[item['Type']](self.args[0], jellyfindb, kodidb, self.args[1]).userdata try: obj(item) except LibraryException as error: if error.status == 'StopCalled': break except Exception as error: LOG.exception(error) self.queue.task_done() if window('jellyfin_should_stop.bool'): break LOG.info("--<[ q:userdata/%s ]", id(self)) self.is_done = True
def run(self): import objects ''' Workaround for playing folders only (context menu on a series/season folder > play) Due to plugin paths being returned within the strm, the entries are mislabelled. Queue items after the first item was setup and started playing via plugin above. ''' xbmc.sleep(200) # Let Kodi catch up LOG.info("-->[ folder play ]") play = None position = 1 # play folder should always create a new playlist. while True: if not window('emby.playlist.plugin.bool' ): # default.py wait for initial item to start up try: try: params = self.server.queue.get(timeout=0.01) except Queue.Empty: break server = params.get('server') if not server and not window('emby_online.bool'): dialog("notification", heading="{emby}", message=_(33146), icon=xbmcgui.NOTIFICATION_ERROR) raise Exception("NotConnected") play = objects.PlayStrm(params, server) position = play.play_folder(position) except Exception as error: LOG.exception(error) xbmc.Player().stop() # mute failed playback pop up xbmc.PlayList(xbmc.PLAYLIST_VIDEO).clear() self.server.queue.queue.clear() break self.server.queue.task_done() self.server.threads.remove(self) self.server.pending = [] LOG.info("--<[ folder play ]")
def stop(self): window('emby.playlist.play', clear=True) window('emby.playlist.ready', clear=True) window('emby.playlist.start', clear=True) window('emby.playlist.audio', clear=True) self.server.threads.remove(self) self.server.pending = [] LOG.info("--<[ queue play ]")
def run(self): with self.lock, self.database as kodidb, Database( 'jellyfin') as jellyfindb: while True: try: item = self.queue.get(timeout=1) except Queue.Empty: break if item['Type'] == 'Movie': obj = Movies(self.args[0], jellyfindb, kodidb, self.args[1]).movie elif item['Type'] == 'Boxset': obj = Movies(self.args[0], jellyfindb, kodidb, self.args[1]).boxset elif item['Type'] == 'Series': obj = TVShows(self.args[0], jellyfindb, kodidb, self.args[1]).tvshow elif item['Type'] == 'Season': obj = TVShows(self.args[0], jellyfindb, kodidb, self.args[1]).season elif item['Type'] == 'Episode': obj = TVShows(self.args[0], jellyfindb, kodidb, self.args[1]).episode elif item['Type'] == 'MusicVideo': obj = MusicVideos(self.args[0], jellyfindb, kodidb, self.args[1]).musicvideo elif item['Type'] == 'MusicAlbum': obj = Music(self.args[0], jellyfindb, kodidb, self.args[1]).album elif item['Type'] == 'MusicArtist': obj = Music(self.args[0], jellyfindb, kodidb, self.args[1]).artist elif item['Type'] == 'AlbumArtist': obj = Music(self.args[0], jellyfindb, kodidb, self.args[1]).albumartist elif item['Type'] == 'Audio': obj = Music(self.args[0], jellyfindb, kodidb, self.args[1]).song try: if obj(item) and self.notify: self.notify_output.put( (item['Type'], api.API(item).get_naming())) except LibraryException as error: if error.status == 'StopCalled': break except Exception as error: LOG.exception(error) self.queue.task_done() if window('jellyfin_should_stop.bool'): break LOG.info("--<[ q:updated/%s ]", id(self)) self.is_done = True
def onSettingsChanged(self): ''' React to setting changes that impact window values. ''' if window('jellyfin_should_stop.bool'): return if settings('logLevel') != self.settings['log_level']: log_level = settings('logLevel') self.settings['logLevel'] = log_level LOG.info("New log level: %s", log_level) if settings('enableContext.bool') != self.settings['enable_context']: window('jellyfin_context', settings('enableContext')) self.settings['enable_context'] = settings('enableContext.bool') LOG.info("New context setting: %s", self.settings['enable_context']) if settings('enableContextTranscode.bool' ) != self.settings['enable_context_transcode']: window('jellyfin_context_transcode', settings('enableContextTranscode')) self.settings['enable_context_transcode'] = settings( 'enableContextTranscode.bool') LOG.info("New context transcode setting: %s", self.settings['enable_context_transcode']) if settings('useDirectPaths' ) != self.settings['mode'] and self.library_thread.started: self.settings['mode'] = settings('useDirectPaths') LOG.info("New playback mode setting: %s", self.settings['mode']) if not self.settings.get('mode_warn'): self.settings['mode_warn'] = True dialog("yesno", "{jellyfin}", translate(33118)) if settings('kodiCompanion.bool') != self.settings['kodi_companion']: self.settings['kodi_companion'] = settings('kodiCompanion.bool') if not self.settings['kodi_companion']: dialog("ok", "{jellyfin}", translate(33138))
def window_single_node(self, index, item_type, view): ''' Single destination node. ''' path = "library://video/jellyfin_%s.xml" % view['Tag'].replace(" ", "") window_path = "ActivateWindow(Videos,%s,return)" % path window_prop = "Jellyfin.nodes.%s" % index window('%s.title' % window_prop, view['Name']) window('%s.path' % window_prop, window_path) window('%s.content' % window_prop, path) window('%s.type' % window_prop, item_type)
def __exit__(self, exc_type, exc_val, exc_tb): ''' Exiting sync ''' self.running = False window('emby_sync', clear=True) if self.screensaver is not None: xbmc.executebuiltin('InhibitIdleShutdown(false)') set_screensaver(value=self.screensaver) self.screensaver = None if self.artwork is not None: settings('enableTextureCache.bool', True) self.artwork = None LOG.info("[ enable artwork cache ]") LOG.info("--<[ fullsync ]")
def StopServer(self, server, data, *args, **kwargs): if not data['ServerId']: window('emby.server.state', clear=True) else: window('emby.server.%s.state' % data['ServerId'], clear=True) current = window('emby.server.states.json') current.pop(current.index(data['ServerId'])) window('emby.server.states.json', current)
def run(self): count = 0 with requests.Session() as s: while True: try: item_ids = self.queue.get(timeout=1) except Queue.Empty: LOG.info("--<[ q:download/%s/%s ]", id(self), count) LOG.info("--[ q:download/remove ] %s", self.removed) break clean_list = [str(x) for x in item_ids] request = { 'type': "GET", 'handler': "Users/{UserId}/Items", 'params': { 'Ids': ','.join(clean_list), 'Fields': api.info() } } try: result = self.server['http/request'](request, s) self.removed.extend( list( set(clean_list) - set([str(x['Id']) for x in result['Items']]))) for item in result['Items']: if item['Type'] in self.output: self.output[item['Type']].put(item) except HTTPException as error: LOG.error("--[ http status: %s ]", error.status) if error.status in ('ServerUnreachable', 'ReadTimeout', 503): self.queue.put(item_ids) break except Exception as error: LOG.exception(error) count += len(clean_list) self.queue.task_done() if window('emby_should_stop.bool'): break self.is_done = True
def get(self): while True: response = window('jellyfin_%s.json' % self.data['VoidName']) if response != "": LOG.debug("--<[ nostromo/jellyfin_%s.json ]", self.data['VoidName']) window('jellyfin_%s' % self.data['VoidName'], clear=True) return response if window('jellyfin_should_stop.bool'): LOG.info("Abandon mission! A black hole just swallowed [ %s/%s ]", self.method, self.data['VoidName']) return xbmc.sleep(100) LOG.info("--[ void/%s ]", self.data['VoidName'])
def window_clear(self, name=None): ''' Clearing window prop setup for Views. ''' total = int(window((name or 'Jellyfin.nodes') + '.total') or 0) props = [ "index", "id", "path", "artwork", "title", "content", "type" "inprogress.content", "inprogress.title", "inprogress.content", "inprogress.path", "nextepisodes.title", "nextepisodes.content", "nextepisodes.path", "unwatched.title", "unwatched.content", "unwatched.path", "recent.title", "recent.content", "recent.path", "recentepisodes.title", "recentepisodes.content", "recentepisodes.path", "inprogressepisodes.title", "inprogressepisodes.content", "inprogressepisodes.path" ] for i in range(total): for prop in props: window('Jellyfin.nodes.%s.%s' % (str(i), prop), clear=True) for prop in props: window('Jellyfin.nodes.%s' % prop, clear=True)
def set_playlist(self, item, listitem, db_id=None, transcode=False, *args, **kwargs): ''' Verify seektime, set intros, set main item and set additional parts. Detect the seektime for video type content. Verify the default video action set in Kodi for accurate resume behavior. ''' seektime = window('emby.resume.bool') window('emby.resume', clear=True) if item['MediaType'] in ('Video', 'Audio'): resume = item['UserData'].get('PlaybackPositionTicks') if resume: if get_play_action() == "Resume": seektime = True if transcode and not seektime: choice = self.resume_dialog( api.API(item, self.server).adjust_resume( (resume or 0) / 10000000.0)) if choice is None: raise Exception("User backed out of resume dialog.") seektime = False if not choice else True if settings('enableCinema.bool') and not seektime: self._set_intros(item) self.set_listitem(item, listitem, db_id, seektime) playutils.set_properties(item, item['PlaybackInfo']['Method'], self.server_id) self.stack.append([item['PlaybackInfo']['Path'], listitem]) if item.get('PartCount'): self._set_additional_parts(item['Id'])
def on_notification(self, sender, method, data): LOG.debug("[ %s: %s ] %s", sender, method, json.dumps(data)) data['MonitorMethod'] = method if self.sleep: LOG.info("System.OnSleep detected, ignore monitor request.") return if method == 'Playlist.OnClear' and data[ 'playlistid'] == self.playlistid: # set in webservice.py window('emby.play.widget', clear=True) server = self._get_server(method, data) self.queue.put(( getattr(self, method.replace('.', '_')), server, data, )) self.add_worker()
def __enter__(self): ''' Open the connection and return the Database class. This is to allow for the cursor, conn and others to be accessible. ''' self.path = self._sql(self.db_file) self.conn = sqlite3.connect(self.path, timeout=self.timeout) self.cursor = self.conn.cursor() if self.db_file in ('video', 'music', 'texture', 'emby'): self.conn.execute("PRAGMA journal_mode=WAL" ) # to avoid writing conflict with kodi LOG.debug("--->[ database: %s ] %s", self.db_file, id(self.conn)) if not window('emby_db_check.bool') and self.db_file == 'emby': window('emby_db_check.bool', True) emby_tables(self.cursor) self.conn.commit() return self
def run(self): import objects ''' Workaround for widgets only playback. Widgets start with a music playlist, this causes bugs in skin, etc. Create a new video playlist for the item and play it. ''' xbmc.sleep(200) # Let Kodi catch up LOG.info("-->[ widget play ]") play = None xbmc.PlayList(xbmc.PLAYLIST_MUSIC).clear() xbmc.PlayList(xbmc.PLAYLIST_VIDEO).clear() objects.utils.enable_busy_dialog() try: server = self.params.get('server') if not server and not window('emby_online.bool'): dialog("notification", heading="{emby}", message=_(33146), icon=xbmcgui.NOTIFICATION_ERROR) raise Exception("NotConnected") play = objects.PlayStrm(self.params, server) play.play() except Exception as error: LOG.exception(error) objects.utils.disable_busy_dialog() xbmc.Player().stop() # mute failed playback pop up xbmc.PlayList(xbmc.PLAYLIST_VIDEO).clear() else: objects.utils.disable_busy_dialog() window('emby.play.widget.bool', True) play.start_playback() self.server.pending = [] LOG.info("--<[ widget play ]")
def _set_intros(self, item): ''' if we have any play them when the movie/show is not being resumed. ''' intros = TheVoid('GetIntros', { 'ServerId': self.server_id, 'Id': item['Id'] }).get() if intros['Items']: enabled = True if settings('askCinema') == "true": resp = dialog("yesno", heading="{jellyfin}", line1=translate(33016)) if not resp: enabled = False LOG.info("Skip trailers.") if enabled: for intro in intros['Items']: listitem = xbmcgui.ListItem() LOG.info("[ intro/%s ] %s", intro['Id'], intro['Name']) play = playutils.PlayUtils(intro, False, self.server_id, self.server) source = play.select_source(play.get_sources()) self.set_listitem(intro, listitem, intro=True) listitem.setPath(intro['PlaybackInfo']['Path']) playutils.set_properties(intro, intro['PlaybackInfo']['Method'], self.server_id) self.stack.append( [intro['PlaybackInfo']['Path'], listitem]) window('jellyfin.skip.%s' % intro['Id'], value="true")
def on_update(data, server): ''' Only for manually marking as watched/unwatched ''' try: kodi_id = data['item']['id'] media = data['item']['type'] playcount = int(data['playcount']) LOG.info(" [ update/%s ] kodi_id: %s media: %s", playcount, kodi_id, media) except (KeyError, TypeError): LOG.debug("Invalid playstate update") return item = database.get_item(kodi_id, media) if item: if not window('jellyfin.skip.%s.bool' % item[0]): server.jellyfin.item_played(item[0], playcount) window('jellyfin.skip.%s' % item[0], clear=True)
def __enter__(self): ''' Do everything we need before the sync ''' LOG.info("-->[ fullsync ]") if not settings('dbSyncScreensaver.bool'): xbmc.executebuiltin('InhibitIdleShutdown(true)') self.screensaver = get_screensaver() set_screensaver(value="") self.running = True if settings('enableTextureCache.bool') and settings('lowPowered.bool'): self.artwork = True settings('enableTextureCache.bool', False) LOG.info("[ disable artwork cache ]") window('emby_sync.bool', True) return self