Ejemplo n.º 1
0
 def scrobble_if_state_changed(self, prev, current):
     """
     Possible race conditions:
     1) start_preview, after __preview_duration__ secs, stop_preview
        start_preview starts preview_timer for " secs, with cleanup=exit_preview.
        the stop_preview also triggers exit_preview, both are run parallelly.
     """
     for action in self.decide_action(prev, current):
         logger.debug(f"action={action}")
         if action == "scrobble":
             logger.debug(current)
             self.scrobble_status(current)
         elif action == "stop_previous":
             self.scrobble_queue.put(("stop", prev))
         elif action == "exit_preview":
             self.exit_preview()
         elif action == "enter_preview":
             assert not self.preview and not self.scrobble_buf, "Invalid state"
             self.preview = True
             self.scrobble_buf = current
             self.preview_timer = ResumableTimer(self.preview_duration,
                                                 self.delayed_scrobble,
                                                 (self.exit_preview, ))
             self.preview_timer.start()
         elif action == "pause_preview":
             self.scrobble_buf = current
             self.preview_timer.pause()
         elif action == "resume_preview":
             self.scrobble_buf = current
             self.preview_timer.resume()
         elif action == "enter_fast_pause":
             assert not self.fast_pause, "Invalid state"
             self.fast_pause = True
         elif action == "clear_buf":
             self.clear_timer('fast_pause_timer')
             self.scrobble_buf = None
         elif action == "delayed_play":
             self.clear_timer('fast_pause_timer')
             self.scrobble_buf = current
             self.fast_pause_timer = ResumableTimer(
                 self.fast_pause_duration,
                 self.delayed_scrobble,
                 (self.exit_fast_pause, ),
             )
             self.fast_pause_timer.start()
         elif action == "exit_fast_pause":
             self.exit_fast_pause()
         else:
             logger.warning(f"Invalid action {action}")
Ejemplo n.º 2
0
def prepare_scrobble_data(title, type, year=None, *args, **kwargs):
    trakt_id = get_trakt_id(title, type, year)
    if trakt_id < 1:
        logger.warning(f"Invalid trakt id for {title}")
        return None
    if type == 'movie':
        return {'movie': {'ids': {'trakt': trakt_id}}}
    elif type == 'episode':
        return {
            'show': {'ids': {'trakt': trakt_id}},
            'episode': {
                'season': kwargs['season'],
                'number': kwargs['episode']
            }
        }
Ejemplo n.º 3
0
 def scrobble(self, verb, data):
     resp = trakt.scrobble(verb, **data)
     if resp:
         if 'movie' in resp:
             name = resp['movie']['title']
         else:
             name = (resp['show']['title'] +
                     " S{season:02}E{number:02}".format(**resp['episode']))
         msg = f"Scrobble {verb} successful for {name}"
         logger.info(msg)
         notify(msg)
         self.backlog_cleaner.clear()
     elif resp is False and verb == 'stop' and data['progress'] > 80:
         logger.warning('Scrobble unsuccessful. Will try again later.')
         self.backlog_cleaner.add(data)
     else:
         logger.warning('Scrobble unsuccessful.')
Ejemplo n.º 4
0
    def run(self):
        while True:
            try:
                self.update_status()
            except requests.ConnectionError:
                logger.info(f'Unable to connect to {self.name}. Ensure that '
                            'the web interface is running.')
                self.status = {}
            except requests.HTTPError as e:
                logger.error(f"Error while getting data from {self.name}: {e}")
                break
            if not self.status.get("filepath") and not self.status.get(
                    "media_info"):
                self.status = {}
            self.handle_status_update()
            time.sleep(self.poll_interval)

        logger.warning(f"{self.name} monitor stopped")
Ejemplo n.º 5
0
 def conn_loop(self):
     sock = socket.socket(socket.AF_UNIX)
     try:
         sock.connect(self.ipc_path)
     except ConnectionRefusedError:
         logger.warning("Connection refused. Maybe we retried too soon?")
         return
     self.is_running = True
     sock_list = [sock]
     while self.is_running:
         r, _, _ = select.select(sock_list, [], [], self.read_timeout)
         if r:  # r == [sock]
             # socket has data to be read
             try:
                 data = sock.recv(4096)
             except ConnectionResetError:
                 self.is_running = False
                 break
             if len(data) == 0:
                 # EOF reached
                 self.is_running = False
                 break
             self.on_data(data)
         while not self.write_queue.empty():
             # block until sock can be written to
             _, w, _ = select.select([], sock_list, [], self.write_timeout)
             if not w:
                 logger.warning(
                     "Timed out writing to socket. Killing connection.")
                 self.is_running = False
                 break
             try:
                 sock.sendall(self.write_queue.get_nowait())
             except BrokenPipeError:
                 self.is_running = False
                 break
             else:
                 self.write_queue.task_done()
     sock.close()
     while not self.write_queue.empty():
         self.write_queue.get_nowait()
         self.write_queue.task_done()
     logger.debug('Sock closed')
Ejemplo n.º 6
0
    def update_status(self):
        if not self.WATCHED_PROPS.issubset(self.vars):
            logger.warning("Incomplete media status info")
            return
        fpath = Path(self.vars['working-directory']) / Path(self.vars['path'])

        # Update last known position if player is stopped
        pos = self.vars['time-pos']
        if self.vars['state'] == 0 and self.status['state'] == 2:
            pos += round(time.time() - self.status['time'], 3)
        pos = min(pos, self.vars['duration'])

        self.status = {
            'state': self.vars['state'],
            'filepath': str(fpath),
            'position': pos,
            'duration': self.vars['duration'],
            'time': time.time()
        }
        self.handle_status_update()
Ejemplo n.º 7
0
 def scrobble(self, verb, data):
     logger.debug(f"Scrobbling {verb} at {data['progress']:.2f}% for "
                  f"{data['media_info']['title']}")
     resp = trakt.scrobble(verb, **data)
     if resp:
         if 'movie' in resp:
             name = resp['movie']['title']
         else:
             name = (resp['show']['title'] +
                     " S{season:02}E{number:02}".format(**resp['episode']))
         category = 'resume' if self.is_resume(verb, data) else verb
         msg = f"Scrobble {category} successful for {name}"
         logger.info(msg)
         notify(msg, category=f"scrobble.{category}")
         self.backlog_cleaner.clear()
     elif resp is False and verb == 'stop' and data['progress'] > 80:
         logger.warning('Scrobble unsuccessful. Will try again later.')
         self.backlog_cleaner.add(data)
     else:
         logger.warning('Scrobble unsuccessful.')
     self.prev_scrobble = (verb, data)
Ejemplo n.º 8
0
def get_trakt_id(title, item_type, year=None):
    required_type = 'show' if item_type == 'episode' else 'movie'

    global trakt_cache
    if not trakt_cache:
        trakt_cache = read_json(TRAKT_CACHE_PATH) or {'movie': {}, 'show': {}}

    key = f"{title}{year or ''}"

    trakt_id = trakt_cache[required_type].get(key)
    if trakt_id:
        return trakt_id

    logger.debug(
        f'Searching trakt: Title: "{title}"{year and f", Year: {year}" or ""}')
    results = search(title, [required_type], year)
    if results == [] and year is not None:
        # no match, possibly a mismatch in year metadata
        msg = (
            f'Trakt search yielded no results for the {required_type}, {title}, '
            f'Year: {year}. Retrying search without filtering by year.')
        logger.warning(msg)
        notify(msg, category="trakt")
        results = search(title, [required_type])  # retry without 'year'

    if results is None:  # Connection error
        return 0  # Dont store in cache
    elif results == [] or results[0]['score'] < 5:  # Weak or no match
        msg = f'Trakt search yielded no results for the {required_type}, {title}'
        msg += f", Year: {year}" * bool(year)
        logger.warning(msg)
        notify(msg, category="trakt")
        trakt_id = -1
    else:
        trakt_id = results[0][required_type]['ids']['trakt']

    trakt_cache[required_type][key] = trakt_id
    logger.debug(f'Trakt ID: {trakt_id}')
    write_json(trakt_cache, TRAKT_CACHE_PATH)
    return trakt_id
Ejemplo n.º 9
0
def scrobble(verb, media_info, progress, *args, **kwargs):
    scrobble_data = prepare_scrobble_data(**media_info)
    if not scrobble_data:
        return None
    scrobble_data['progress'] = progress
    scrobble_params = {
        "url": API_URL + '/scrobble/' + verb,
        "headers": trakt_auth.headers,
        "json": scrobble_data,
        "timeout": 30,
    }
    scrobble_resp = safe_request('post', scrobble_params)

    if scrobble_resp is not None:
        if scrobble_resp.status_code == HTTPStatus.NOT_FOUND:
            logger.warning("Not found on trakt. The media info is incorrect.")
            return None
        elif scrobble_resp.status_code == HTTPStatus.CONFLICT:
            logger.warning("Scrobble already exists on trakt server.")
            return None

    return scrobble_resp.json() if scrobble_resp else False
Ejemplo n.º 10
0
def main():
    assert get_access_token()
    scrobble_queue = Queue()
    backlog_cleaner = BacklogCleaner()
    scrobbler = Scrobbler(scrobble_queue, backlog_cleaner)
    scrobbler.start()

    allowed_monitors = config['players']['monitored'].get(confuse.StrSeq(default=[]))
    all_monitors = collect_monitors()

    unknown = set(allowed_monitors).difference(Mon.name for Mon in all_monitors)
    if unknown:
        logger.warning(f"Unknown player(s): {', '.join(unknown)}")

    for Mon in all_monitors:
        if Mon.name not in allowed_monitors:
            continue
        mon = Mon(scrobble_queue)
        if not mon or not mon._initialized:
            logger.warning(f"Could not start monitor for {Mon.name}")
            continue
        mon.start()
Ejemplo n.º 11
0
def get_trakt_id(title, item_type):
    required_type = 'show' if item_type == 'episode' else 'movie'

    logger.debug('Searching cache.')
    trakt_id = trakt_cache[required_type].get(title)
    if trakt_id:
        return trakt_id

    logger.debug('Searching trakt.')
    results = search(title, [required_type])
    if results is None:  # Connection error
        return 0  # Dont store in cache
    elif results == [] or results[0]['score'] < 5:  # Weak or no match
        logger.warning('Trakt search yielded no results.')
        notify('Trakt search yielded no results for ' + title)
        trakt_id = -1
    else:
        trakt_id = results[0][required_type]['ids']['trakt']

    trakt_cache[required_type][title] = trakt_id
    logger.debug(f'Trakt ID: {trakt_id}')
    write_json(trakt_cache, TRAKT_CACHE_PATH)
    return trakt_id
Ejemplo n.º 12
0
        from win10toast import ToastNotifier
        toaster = ToastNotifier()
    elif sys.platform == 'darwin':
        import subprocess as sp
    else:
        try:
            from jeepney import DBusAddress, new_method_call
            from jeepney.io.blocking import open_dbus_connection
        except (ImportError, ModuleNotFoundError):
            import subprocess as sp
            notifier = None
        else:
            try:
                dbus_connection = open_dbus_connection(bus='SESSION')
            except Exception as e:
                logger.warning(f"Could not connect to DBUS: {e}")
                logger.warning("Disabling notifications")
                enabled_categories.clear()
                notifier = None
            else:
                notifier = DBusAddress(
                    '/org/freedesktop/Notifications',
                    bus_name='org.freedesktop.Notifications',
                    interface='org.freedesktop.Notifications')


def dbus_notify(title, body, timeout):
    msg = new_method_call(
        notifier,
        'Notify',
        'susssasa{sv}i',