def visulize_intro_from_hashes(videofile, hashes, pause=0.01, end=600): # pragma: no cover """Play the frames that maches the hashes.""" import matplotlib.pyplot as plt import cv2 first_vid = video_frame_by_frame(videofile, frame_range=False, end=end) ax1 = plt.subplot(1, 2, 1) im1 = ax1.imshow(np.zeros([150, 150, 3], dtype=np.uint8)) ax1.set_xlabel(os.path.basename(videofile)) for first_frame, first_pos in first_vid: h = ImageHash(create_imghash(first_frame)) H = str(h) if h and H in hashes: # Convert as the colors are off for matplotlib. vis_frame = cv2.cvtColor(first_frame, cv2.COLOR_BGR2RGB) ax1.set_title('Source %s | %s' % (to_time(first_pos / 1000), H)) im1.set_data(vis_frame) plt.pause(pause) #plt.ioff() # due to infinite loop, this gets never called. plt.show()
def client_jump_to(offset=None, sessionkey=None): """Seek the client to the offset. Args: offset(int): Default None sessionkey(int): So we made sure we control the correct client. Returns: None """ global JUMP_LIST LOG.debug('Called jump with %s %s %s', offset, to_time(offset), sessionkey) if offset == -1: return conf_clients = CONFIG.get('clients', []) conf_users = CONFIG.get('users', []) for media in PMS.sessions(): # Find the client.. This client does not have the correct address # or 'protocolCapabilities' so we have to get the correct one. # or we can proxy thru the server.. if sessionkey and int(sessionkey) == media.sessionKey: client = media.players[0] user = media.usernames[0] LOG.debug('client %s %s %s', client.title, user, (media.viewOffset / 1000)) # Check that this client is allowed. if conf_clients and client.title not in conf_clients: LOG.debug('client %s is not allowed', client.title) return # Check that this user is allowed. if conf_users and user not in conf_users: LOG.debug('user %s is not allowed', user) return # To stop processing. from func task if we have used to much time.. # This will not work if/when credits etc are added. Need a better way. # if offset <= media.viewOffset / 1000: # LOG.debug('Didnt jump because of offset') # return # This does not work on plex web since the f****r returns # the local url.. LOG.debug('connecting to client') client = PMS.client(client.title).connect() LOG.debug('calling seekTo') client.seekTo(int(offset * 1000)) LOG.debug('Jumped %s %s to %s %s', user, client.title, offset, media._prettyfilename()) # Some clients needs some time.. # time.sleep(0.2) # client.play() JUMP_LIST.remove(sessionkey) #time.sleep(1) return
def process_to_db(media, theme=None, vid=None, start=None, end=None, ffmpeg_end=None, recap=None): """Process a plex media item to the db Args: media (Episode obj): theme: path to the theme. vid: path to the stripped wav of the media item. start (None, int): of theme. end (None, int): of theme. ffmpeg_end (None, int): What does ffmpeg think is the start of the ep. Returns: None """ global HT ff = -1 name = media._prettyfilename() LOG.debug('Started to process %s', name) if theme is None: theme = get_theme(media) theme = convert_and_trim(theme, fs=11025, theme=True) if vid is None: vid = convert_and_trim(check_file_access(media), fs=11025, trim=600) # too cover manual process_to_db. if theme not in HT.names: analyzer().ingest(HT, theme) # Lets skip the start time for now. This need to be added later to support shows # that have show, theme song show. if end is None: start, end = get_offset_end(vid, HT) if ffmpeg_end is None: ffmpeg_end = find_offset_ffmpeg(check_file_access(media)) if recap is None: recap = has_recap(media, CONFIG.get('words'), audio=vid) if end is not None: with session_scope() as se: try: se.query(Preprocessed).filter_by(ratingKey=media.ratingKey).one() except NoResultFound: p = Preprocessed(show_name=media.grandparentTitle, ep_title=media.title, theme_end=end, theme_start=start, theme_start_str=to_time(start), theme_end_str=to_time(end), ffmpeg_end=ffmpeg_end, ffmpeg_end_str=to_time(ff), duration=media.duration, ratingKey=media.ratingKey, grandparentRatingKey=media.grandparentRatingKey, prettyname=media._prettyfilename(), updatedAt=media.updatedAt, has_recap=recap) se.add(p) LOG.debug('Added %s to media.db', name)
def check(data): global JUMP_LIST if data.get('type') == 'playing' and data.get( 'PlaySessionStateNotification'): sess = data.get('PlaySessionStateNotification')[0] if sess.get('state') != 'playing': return ratingkey = sess.get('ratingKey') sessionkey = int(sess.get('sessionKey')) progress = sess.get('viewOffset', 0) / 1000 # converted to sec. mode = CONFIG.get('mode', 'skip_only_theme') # This has to be removed if/when credits are added. if progress >= 600: return def best_time(item): """Find the best time in the db.""" if item.correct_theme_end and item.correct_theme_end != 1: sec = item.correct_theme_end elif item.correct_ffmpeg and item.correct_ffmpeg != 1: sec = item.correct_ffmpeg elif item.theme_end and item.theme_end != -1: sec = item.theme_end elif item.ffmpeg_end and item.ffmpeg_end != -1: sec = item.ffmpeg_end else: sec = -1 return sec def jump(item, sessionkey, sec=None): if sec is None: sec = best_time(item) if sessionkey not in JUMP_LIST: JUMP_LIST.append(sessionkey) POOL.apply_async(client_jump_to, args=(sec, sessionkey)) with session_scope() as se: try: item = se.query(Preprocessed).filter_by(ratingKey=ratingkey).one() if item: LOG.debug('Found %s theme start %s, theme end %s, progress %s', item.prettyname, item.theme_start_str, item.theme_end_str, to_time(progress)) bt = best_time(item) if mode == 'skip_if_recap' and item.theme_end and item.theme_start: return jump(item, sessionkey, bt) if mode == 'skip_only_theme': if item.correct_theme_end and item.correct_theme_start: if progress > item.correct_theme_start and progress < item.correct_theme_end: LOG.debug('%s is in the correct time range correct_theme_end', item.prettyname) return jump(item, sessionkey, item.correct_theme_end) elif item.theme_end and item.theme_start: if progress > item.theme_start and progress < item.theme_end: LOG.debug('%s is in the correct time range theme_end', item.prettyname) return jump(item, sessionkey, item.theme_end) #if progress > item.theme_start and progress < item.theme_end: # LOG.debug('%s is in the correct time range', item.prettyname) # return jump(item, sessionkey, item.correct_theme_end or item.theme_end) #else: # if item.theme_start - progress < 0: # n = item.theme_start - progress # if n > 0: # part = 'jumping in %s' % to_time(n) # else: # part = 'should have jumped %s ago' % to_time(n) # LOG.debug('Skipping %s as it not in the correct time range jumping in %s', # item.prettyname, part) except NoResultFound: if ratingkey not in IN_PROG: IN_PROG.append(ratingkey) LOG.debug('Failed to find %s in the db', ratingkey) POOL.apply_async(task, args=(ratingkey, sessionkey))
def check(data): global JUMP_LIST if data.get('type') == 'playing' and data.get( 'PlaySessionStateNotification'): sess = data.get('PlaySessionStateNotification')[0] if sess.get('state') != 'playing': return ratingkey = int(sess.get('ratingKey')) sessionkey = int(sess.get('sessionKey')) progress = sess.get('viewOffset', 0) / 1000 # converted to sec. mode = CONFIG['general'].get('mode', 'skip_only_theme') no_wait_tick = CONFIG['general'].get('no_wait_tick', 0) def best_time(item): """Find the best time in the db.""" if item.type == 'episode' and item.correct_theme_end and item.correct_theme_end != 1: sec = item.correct_theme_end elif item.correct_ffmpeg and item.correct_ffmpeg != 1: sec = item.correct_ffmpeg elif item.type == 'episode' and item.theme_end and item.theme_end != -1: sec = item.theme_end elif item.ffmpeg_end and item.ffmpeg_end != -1: sec = item.ffmpeg_end else: sec = -1 return sec def jump(item, sessionkey, sec=None, action=None): # pragma: no cover if sec is None: sec = best_time(item) if action: POOL.apply_async(client_action, args=(sec, sessionkey, action)) return if sessionkey not in JUMP_LIST: LOG.debug('Called jump with %s %s %s %s', item.prettyname, sessionkey, sec, action) JUMP_LIST.append(sessionkey) POOL.apply_async(client_action, args=(sec, sessionkey, action)) with session_scope() as se: try: item = se.query(Processed).filter_by(ratingKey=ratingkey).one() if item: bt = best_time(item) LOG.debug( 'Found %s theme start %s, theme end %s, ffmpeg_end %s progress %s ' 'best_time %s credits_start %s credits_end %s', item.prettyname, item.theme_start_str, item.theme_end_str, item.ffmpeg_end_str, to_time(progress), to_time(bt), item.credits_start_str, item.credits_end_str) if (item.type == 'episode' and CONFIG['tv'].get('check_credits') is True and CONFIG['tv'].get('check_credits_action') == 'stop' or item.type == 'movie' and CONFIG['movie'].get('check_credits') is True and CONFIG['movie'].get('check_credits_action') == 'stop'): # todo check for correct credits too if item.credits_start and item.credits_start != -1 and progress >= item.credits_start: LOG.debug('We found the start of the credits.') return jump(item, sessionkey, item.credits_start, action='stop') # Let's try to not wait for the next tick. progress = progress + no_wait_tick # If recap is detected just instantly skip to intro end. # Now this can failed is there is: recap, new episode stuff, intro, new episode stuff # So thats why skip_only_theme is default as its the safest option. if (mode == 'skip_if_recap' and item.type == 'episode' and item.has_recap is True and bt != -1): return jump(item, sessionkey, bt) # This mode will allow playback until the theme starts so it should be faster then skip_if_recap. if mode == 'skip_only_theme': # For manual corrected themes.. if item.type == 'episode' and item.correct_theme_end and item.correct_theme_start: if progress > item.correct_theme_start and progress < item.correct_theme_end: LOG.debug( '%s is in the correct time range correct_theme_end', item.prettyname) return jump(item, sessionkey, item.correct_theme_end) elif item.type == 'episode' and item.theme_end and item.theme_start: if progress > item.theme_start and progress < item.theme_end: LOG.debug( '%s is in the correct time range theme_end', item.prettyname) return jump(item, sessionkey, item.theme_end) except NoResultFound: if ratingkey not in IN_PROG: IN_PROG.append(ratingkey) LOG.debug('Failed to find ratingkey %s in the db', ratingkey) ret = POOL.apply_async(task, args=(ratingkey, sessionkey)) return ret elif data.get('type') == 'timeline': timeline = data.get('TimelineEntry')[0] state = timeline.get('state') ratingkey = timeline.get('itemID') title = timeline.get('title') metadata_type = timeline.get('type') identifier = timeline.get('identifier') metadata_state = timeline.get('metadataState') if (metadata_type in (1, 4) and state == 0 and metadata_state == 'created' and identifier == 'com.plexapp.plugins.library'): LOG.debug('%s was added to %s', title, PMS.friendlyName) # Youtubedl can fail if we batch add loads of eps at the same time if there is no # theme. if (metadata_type == 1 and not CONFIG['movie'].get('process_recently_added') or metadata_state == 4 and not CONFIG['tv'].get('process_recently_added')): LOG.debug( "Didnt start to process %s is process_recently_added is disabled" ) return if ratingkey not in IN_PROG: IN_PROG.append(ratingkey) ep = PMS.fetchItem(int(ratingkey)) ret = POOL.apply_async(process_to_db, args=(ep, )) return ret elif (metadata_type in (1, 4) and state == 9 and metadata_state == 'deleted'): if (metadata_type == 1 and not CONFIG['movie'].get('process_deleted') or metadata_state == 4 and not CONFIG['tv'].get('process_deleted')): LOG.debug( "Didnt start to process %s is process_deleted is disabled for" ) return with session_scope() as se: try: item = se.query(Processed).filter_by( ratingKey=ratingkey).one() item.delete() LOG.debug('%s was deleted from %s and from media.db', title, PMS.friendlyName) except NoResultFound: LOG.debug('%s was deleted from %s', title, PMS.friendlyName)
def client_action(offset=None, sessionkey=None, action='jump'): # pragma: no cover """Seek the client to the offset. Args: offset(int): Default None sessionkey(int): So we made sure we control the correct client. Returns: None """ global JUMP_LIST LOG.info('Called client_action with %s %s %s %s', offset, to_time(offset), sessionkey, action) def proxy_on_fail(func): import plexapi @wraps(func) def inner(): try: func() except plexapi.exceptions.BadRequest: try: LOG.info( 'Failed to reach the client directly, trying via server.' ) correct_client.proxyThroughServer() func() except: # pragma: no cover correct_client.proxyThroughServer(value=False) raise if offset == -1: return conf_clients = CONFIG.get('general', {}).get('clients', []) conf_users = CONFIG.get('general', {}).get('users', []) correct_client = None clients = PMS.clients() for media in PMS.sessions(): # Find the client.. This client does not have the correct address # or 'protocolCapabilities' so we have to get the correct one. # or we can proxy thru the server.. if sessionkey and int(sessionkey) == media.sessionKey: client = media.players[0] user = media.usernames[0] LOG.info('client %s %s', client.title, (media.viewOffset / 1000)) # Check that this client is allowed. if conf_clients and client.title not in conf_clients: LOG.info('Client %s is not whitelisted', client.title) return # Check that this user is allowed. if conf_users and user not in conf_users: LOG.info('User %s is not whitelisted', user) return # To stop processing. from func task if we have used to much time.. # This will not work if/when credits etc are added. Need a better way. # if offset <= media.viewOffset / 1000: # LOG.debug('Didnt jump because of offset') # return for c in clients: LOG.info('%s %s' % (c.machineIdentifier, client.machineIdentifier)) # So we got the correct client.. if c.machineIdentifier == client.machineIdentifier: # Plex web sometimes add loopback.. if '127.0.0.1' in c._baseurl: c._baseurl = c._baseurl.replace( '127.0.0.1', client.address) correct_client = c break if correct_client: try: LOG.info('Connectiong to %s', correct_client.title) correct_client.connect() except requests.exceptions.ConnectionError: LOG.exception('Cant connect to %s', client.title) return if action != 'stop': if ignore_ratingkey( media, CONFIG['general'].get('ignore_intro_ratingkeys')): LOG.info( 'Didnt send seek command this show, season or episode is ignored' ) return # PMP seems to be really picky about timeline calls, if we dont # it returns 406 errors after 90 sec. if correct_client.product == 'Plex Media Player': correct_client.sendCommand('timeline/poll', wait=0) proxy_on_fail(correct_client.seekTo(int(offset * 1000))) LOG.info('Jumped %s %s to %s %s', user, client.title, offset, media._prettyfilename()) else: if not ignore_ratingkey( media, CONFIG['general'].get('ignore_intro_ratingkeys')): proxy_on_fail(correct_client.stop()) # We might need to login on pms as the user.. # urs_pms = users_pms(PMS, user) # new_media = urs_pms.fetchItem(int(media.ratingkey)) # new_media.markWatched() # LOG.debug('Stopped playback on %s and marked %s as watched.', client.title, media._prettyfilename()) # Check if we just start the next ep instantly. if CONFIG['tv'].get( 'check_credits_start_next_ep') is True: nxt = find_next( media) # This is always false for movies. if nxt: LOG.info('Start playback on %s with %s', user, nxt._prettyfilename()) proxy_on_fail(correct_client.playMedia(nxt)) else: LOG.info('Didnt find the correct client.') # Some clients needs some time.. # time.sleep(0.2) # client.play() # JUMP_LIST.remove(sessionkey) # time.sleep(1) return
def process_to_db(media, theme=None, vid=None, start=None, end=None, ffmpeg_end=None, recap=None, credits_start=None, credits_end=None): """Process a plex media item to the db Args: media (Episode obj): theme: path to the theme. vid: path to the stripped wav of the media item. start (None, int): of theme. end (None, int): of theme. ffmpeg_end (None, int): What does ffmpeg think is the start of the ep. recap(None, bool): If this how has a recap or not credits_start(None, int): The offset (in sec) the credits text starts credits_end(None, int): The offset (in sec) the credits text ends Returns: None """ global HT # Disable for now. # if media.TYPE == 'movie': # return # This will download the theme and add it to # the hashtable if its missing if media.TYPE == 'episode' and theme is None: if HT.has_theme(media, add_if_missing=False) is False: LOG.debug('downloading theme from process_to_db') theme = download_theme(media, HT) name = media._prettyfilename() LOG.debug('Started to process %s', name) if vid is None and media.TYPE == 'episode': vid = convert_and_trim(check_file_access(media), fs=11025, trim=CONFIG['tv'].get('check_for_theme_sec', 600)) # Find the start and the end of the theme in the episode file. if end is None and media.TYPE == 'episode': start, end = get_offset_end(vid, HT) # Guess when the intro ended using blackframes and audio silence. if ffmpeg_end is None: if media.TYPE == 'episode': trim = CONFIG['tv'].get('check_intro_ffmpeg_sec') else: trim = CONFIG['movie'].get('check_intro_ffmpeg_sec') ffmpeg_end = find_offset_ffmpeg(check_file_access(media), trim=trim) # Check for recap. if recap is None: recap = has_recap(media, CONFIG['tv'].get('words', []), audio=vid) if (media.TYPE == 'episode' and CONFIG['tv'].get('check_credits') is True and credits_start is None and credits_end is None): dur = media.duration / 1000 - CONFIG['tv'].get('check_credits_sec', 120) credits_start, credits_end = find_credits(check_file_access(media), offset=dur, check=-1) elif (media.TYPE == 'movie' and CONFIG['movie'].get('check_credits') is True and credits_start is None and credits_end is None): dur = media.duration / 1000 - CONFIG['movie'].get( 'check_credits_sec', 600) credits_start, credits_end = find_credits(check_file_access(media), offset=dur, check=-1) else: # We dont want to find the credits. credits_start = -1 credits_end = -1 # We assume this is kinda right, # double check this # TODO location = list(i.file for i in media.iterParts() if i)[0] with session_scope() as se: try: se.query(Processed).filter_by(ratingKey=media.ratingKey).one() except NoResultFound: if media.TYPE == 'episode': p = Processed(show_name=media.grandparentTitle, title=media.title, type=media.TYPE, theme_end=end, theme_start=start, theme_start_str=to_time(start), theme_end_str=to_time(end), ffmpeg_end=ffmpeg_end, ffmpeg_end_str=to_time(ffmpeg_end), credits_start=credits_start, credits_start_str=to_time(credits_start), credits_end=credits_end, credits_end_str=to_time(credits_end), duration=media.duration, ratingKey=media.ratingKey, grandparentRatingKey=media.grandparentRatingKey, prettyname=media._prettyfilename(), updatedAt=media.updatedAt, has_recap=recap, location=location) elif media.TYPE == 'movie': p = Processed(title=media.title, type=media.TYPE, ffmpeg_end=ffmpeg_end, ffmpeg_end_str=to_time(ffmpeg_end), credits_start=credits_start, credits_start_str=to_time(credits_start), credits_end=credits_end, credits_end_str=to_time(credits_end), duration=media.duration, ratingKey=media.ratingKey, prettyname=media._prettyfilename(), updatedAt=media.updatedAt, location=location) se.add(p) LOG.debug('Added %s to media.db', name) if media.TYPE == 'movie' and CONFIG['movie']['create_edl']: edl.write_edl( location, edl.db_to_edl(p, type=CONFIG['movie']['edl_action_type'])) elif media.TYPE == 'episode' and CONFIG['tv']['create_edl']: edl.write_edl( location, edl.db_to_edl(p, type=CONFIG['tv']['edl_action_type']))
def process_to_db(media, theme=None, vid=None, start=None, end=None, ffmpeg_end=None, recap=None): """Process a plex media item to the db Args: media (Episode obj): theme: path to the theme. vid: path to the stripped wav of the media item. start (None, int): of theme. end (None, int): of theme. ffmpeg_end (None, int): What does ffmpeg think is the start of the ep. Returns: None """ global HT # This will download the theme and add it to # the hashtable if its missing if theme is None: if HT.has_theme(media, add_if_missing=False) is False: LOG.debug('downloading theme from process_to_db') theme = download_theme(media, HT) ff = -1 name = media._prettyfilename() LOG.debug('Started to process %s', name) if vid is None: vid = convert_and_trim(check_file_access(media), fs=11025, trim=600) # Find the start and the end of the theme in the video file. if end is None: start, end = get_offset_end(vid, HT) # Guess when the intro ended using blackframes and audio silence. if ffmpeg_end is None: ffmpeg_end = find_offset_ffmpeg(check_file_access(media)) # Check for recap. if recap is None: recap = has_recap(media, CONFIG.get('words', []), audio=vid) with session_scope() as se: try: se.query(Preprocessed).filter_by(ratingKey=media.ratingKey).one() except NoResultFound: p = Preprocessed(show_name=media.grandparentTitle, ep_title=media.title, theme_end=end, theme_start=start, theme_start_str=to_time(start), theme_end_str=to_time(end), ffmpeg_end=ffmpeg_end, ffmpeg_end_str=to_time(ffmpeg_end), duration=media.duration, ratingKey=media.ratingKey, grandparentRatingKey=media.grandparentRatingKey, prettyname=media._prettyfilename(), updatedAt=media.updatedAt, has_recap=recap) se.add(p) LOG.debug('Added %s to media.db', name)