Пример #1
0
def cli(debug, username, password, servername, url, token, config, verify_ssl):
    """ Entry point for the CLI."""
    global PMS
    global CONFIG

    if debug:
        LOG.setLevel(logging.DEBUG)
    else:
        LOG.setLevel(logging.INFO)

    if config and os.path.isfile(config):
        CONFIG = read_or_make(config)

    url = url or CONFIG.get('url')
    token = token or CONFIG.get('token')
    verify_ssl = verify_ssl or CONFIG.get('verify_ssl')

    if url and token or username and password:

        PMS = get_pms(url=url,
                      token=token,
                      username=username,
                      password=password,
                      servername=servername,
                      verify_ssl=verify_ssl)
Пример #2
0
def get_pms(url=None,
            token=None,
            username=None,
            password=None,
            servername=None,
            verify_ssl=None):
    from plexapi.myplex import MyPlexAccount
    from plexapi.server import PlexServer

    url = url or CONFIG.get('url')
    token = token or CONFIG.get('token')
    verify_ssl = verify_ssl or CONFIG.get('verify_ssl', False)

    if url and token:
        sess = requests.Session()
        if not verify_ssl:
            sess.verify = False
        PMS = PlexServer(url, token, sess)

    elif username and password and servername:
        acc = MyPlexAccount(username, password)
        PMS = acc.resource(servername).connect()

    LOG.debug('Getting server %s', PMS.friendlyName)

    return PMS
Пример #3
0
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
Пример #4
0
def check_real_file_access(path):
    if os.path.exists(path):
        return path

    for key, value in CONFIG.get('remaps', {}).items():
        fp = path.replace(key, value)
        if os.path.exists(fp):
            return fp
Пример #5
0
def download_theme(media, ht, theme_source=None, url=None):
    if media.TYPE == 'show':
        name = media.title
        rk = media.ratingKey
        _theme = media.theme
    else:
        name = media.grandparentTitle
        rk = media.grandparentRatingKey
        _theme = media.grandparentTheme
        if _theme is None:
            _theme = media.show().theme

    pms = media._server

    if theme_source is None:
        theme_source = CONFIG.get('theme_source', 'plex')

    if theme_source == 'youtube':
        theme = search_for_theme_youtube(name, rk, THEMES, url=url)

    elif theme_source == 'tvtunes':
        theme = search_tunes(name, rk, url=url)
        theme = list(itertools.chain.from_iterable(theme.values()))

    elif theme_source == 'plex':
        theme = pms.url(_theme, includeToken=True)
        LOG.debug('Downloading theme via plex %s', theme)

    elif theme_source == 'all':
        theme = []
        st = search_tunes(name, rk, url=url)
        st_res = list(itertools.chain.from_iterable(st.values()))
        theme.extend(st_res)
        theme.append(pms.url(_theme, includeToken=True))
        theme.append(search_for_theme_youtube(name, rk, THEMES, url=url))

    if not isinstance(theme, list):
        theme = [theme]

    final = []
    for th in theme:
        LOG.debug('Download theme using source %s', th)
        # Filename is just added so we can pass a url to convert_and_trim
        th = convert_and_trim(th,
                              fs=11025,
                              theme=True,
                              filename='%s__%s__%s' %
                              (name, rk, int(time.time())))
        analyzer().ingest(ht, th)
        final.append(th)

    return final
Пример #6
0
def create_edl_path(path):
    """Convert a file with a ext to .edl ext."""
    from bw_plex import CONFIG
    if not os.path.exists(path):
        for key, value in CONFIG.get('remaps'):
            fp = path.replace(key, value)
            if os.path.exists(fp):
                path = fp
                break

    f_without_ext = os.path.splitext(path)[0]
    edl_path = f_without_ext + '.edl'
    return edl_path
Пример #7
0
def check_file_access(m):
    """Check if we can reach the file directly
       or if we have to download it via PMS.

       Args:
            m (plexapi.video.Episode)

       Return:
            filepath or http to the file.

    """
    LOG.debug('Checking if we can reach %s directly', m._prettyfilename())

    files = list(m.iterParts())
    # Now we could get the "wrong" file here.
    # If the user has duplications we might return the wrong file
    # CBA with fixing this as it requires to much work :P
    # And the use case is rather slim, you should never have dupes.
    # If the user has they can remove them using plex-cli.
    for file in files:

        if os.path.exists(file.file):
            LOG.debug('Found %s', file.file)
            return file.file
        elif CONFIG.get('remaps', []):
            for key, value in CONFIG.get('remaps'):
                fp = file.file.replace(key, value)
                if os.path.exists(fp):
                    LOG.debug('Found %s', fp)
                    return fp
        else:
            LOG.warning('Downloading from pms..')
            try:
                # for plexapi 3.0.6 and above.
                return PMS.url('%s?download=1' % file.key, includeToken=True)
            except TypeError:
                return PMS.url('%s?download=1' % file.key)
Пример #8
0
def has_recap_audio(audio, phrase=None, thresh=1, duration=30):
    """ audio is wave in 16k sample rate."""
    import speech_recognition as sr

    if phrase is None:
        phrase = CONFIG.get('words')

    try:
        r = sr.Recognizer()
        with sr.AudioFile(audio) as source:
            r.adjust_for_ambient_noise(source)
            audio = r.record(source, duration=duration)
            result = r.recognize_sphinx(audio,
                                        keyword_entries=[(i, thresh)
                                                         for i in phrase])
            LOG.debug('Found %s in audio', result)
            return result

    except sr.UnknownValueError:
        pass

    return False
Пример #9
0
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)
Пример #10
0
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))
Пример #11
0
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
Пример #12
0
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)
Пример #13
0
    from multiprocessing.dummy import ThreadPool as Pool

import click
import requests
from sqlalchemy.orm.exc import NoResultFound

from bw_plex import FP_HASHES, CONFIG, THEMES, LOG, INI_FILE
from bw_plex.config import read_or_make
from bw_plex.credits import find_credits
from bw_plex.db import session_scope, Processed
from bw_plex.misc import (analyzer, convert_and_trim, choose, find_next,
                          find_offset_ffmpeg, get_offset_end, get_pms,
                          get_hashtable, has_recap, to_sec, to_time,
                          download_theme, ignore_ratingkey)

POOL = Pool(int(CONFIG.get('thread_pool_number', 10)))
PMS = None
IN_PROG = []
JUMP_LIST = []
SHOWS = {}
HT = None

is_64bit = struct.calcsize('P') * 8
if not is_64bit:
    LOG.info('You not using a python 64 bit version.')


def log_exception(func):
    @wraps(func)
    def inner(*args, **kwargs):
        try:
Пример #14
0
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
    add_images = False

    # 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']))

        if media.TYPE == 'episode':
            try:
                # since it will check every ep if will download hashes from every ep. We might get
                # away with just checking 2-4 eps. Should this be a config option?
                # we could checkfor grandparentkey and see if we have the required amount
                n = se.query(Images).filter_by(ratingKey=media.ratingKey).one()
            except NoResultFound:
                add_images = True

    if media.TYPE == 'episode' and CONFIG.get('hashing').get(
            'check_frames') and add_images:
        img_hashes = []
        for imghash, frame, pos in hash_file(check_file_access(
                media)):  # Add config option of get frames ever n.
            img = Images(ratingKey=media.ratingKey,
                         hex=str(imghash),
                         hash=imghash.hash.tostring(),
                         grandparentRatingKey=media.grandparentRatingKey,
                         offset=pos,
                         time=to_time(pos))
            img_hashes.append(img)

        with session_scope() as ssee:
            ssee.add_all(img_hashes)