def getJustWatch(title, jw_id, rt_score, streams, jw = None): if jw == None: jw = JustWatch(country = 'US') sel = 'n' ## JustWatch breaks if you bombard it too much, so use a VPN while True: try: res = jw.get_title(title_id = int(jw_id)) except Exception as e: if e.response.status_code == 500: print("No match found for this JustWatch ID {}.".format(jw_id)) return jw_id, rt_score, streams if e.response.status_code == 404: print("No match found for this JustWatch ID {}.".format(jw_id)) return jw_id, rt_score, streams print(e.response.status_code) print("JustWatch not reached. Try again...") print("** Rate Limit was likely exceeded. Please use VPN. **") nada = input("Press [enter] to continue once VPN is turned on.") continue else: print("* MATCHED JustWatch") break if 'error' in res: print("No match found for this JustWatch ID {}.".format(jw_id)) return jw_id, rt_score, streams jw_id, year, desc, runtime, rt_score, streams = parseJustWatch(res) return jw_id, rt_score, streams
def findJustWatch(title, jw = None, jw_genres = None, imdb_id = None, tmdb_id = None): if jw == None: jw = JustWatch(country = 'US') if jw_genres is None: jw_genres = jw.get_genres() sel = 'n' jw_id = np.nan year = np.nan desc = None runtime = np.nan rt_score = np.nan gs = [] streams = [] jw_title = None res = jw.search_for_item(query = title) while sel != 'y' and 'total_results' in res and res['total_results'] > 0: for r in res['items']: if 'scoring' in r: provs = pd.DataFrame(r['scoring']) if (imdb_id != None and len(provs.value[provs.provider_type == 'imdb:id'].values) != 0): if provs.value[provs.provider_type == 'imdb:id'].values != imdb_id: next if (tmdb_id != None and len(provs.value[provs.provider_type == 'tmdb:id'].values) != 0): if provs.value[provs.provider_type == 'tmdb:id'].values != tmdb_id: next jw_title = unidecode(r['title']).replace(',', '') if jw_title is not None and title is not None: if jw_title.lower() == title.lower(): sel = 'y' elif title.lower() in jw_title.lower() or jw_title.lower() in title.lower(): sel = input( "Matching '{}' with JustWatch '{}' ({})... OK? [y or n] ".format(title, jw_title, r['id']) ).lower() if sel == 'y': jw_id, year, desc, runtime, rt_score, streams = parseJustWatch(r) break else: print("Trying again...") break if sel != 'y': print("Unable to find match in JustWatch for '{}'".format(title)) jw_id = tryFloat(input("What is the JustWatch ID? "), get = True) if jw_id == '': jw_id = np.nan rt_score = tryFloat(input("What is the Rotten Tomatoes score? "), get = True) if rt_score == '': rt_score = np.nan user_streams = input("On which services is it streaming? (separated by commas) ") if user_streams != '': streams = [x.strip() for x in user_streams.split(',')] jw_title = None else: print("* MATCHED JustWatch") ## get genres if not np.isnan(jw_id): full_res = jw.get_title(title_id = int(jw_id)) gs = parseGenres(full_res['genre_ids'], jw_genres) if 'genre_ids' in full_res.keys() else [] return jw_id, year, desc, runtime, rt_score, gs, streams, jw_title
def main(wf): from justwatch import JustWatch parser = argparse.ArgumentParser() parser.add_argument('-s', '--search_keyword') parser.add_argument('-t', '--title_id') parser.add_argument('-i', '--input_locale') args = parser.parse_args() search_keyword = args.search_keyword title_id = args.title_id constants.LOCALE = args.input_locale just_watch = JustWatch(country=constants.LOCALE) providers = Providers(just_watch) t = thumbs() if search_keyword is not None: media_items = MediaItems(just_watch, sys.argv[len(sys.argv) - 1], providers).get_alfred_json_list() should_rerun = process_thumbnails_search(t, media_items) wf.logger.error("should_rerun = %s" % should_rerun) show_results_search(media_items, should_rerun) elif title_id is not None: title_id, content_type = parse_title_id(title_id) media_item = MediaItem( just_watch.get_title(title_id=title_id, content_type=content_type), providers) should_rerun = process_thumbnails_providers(t, media_item) show_results_providers(media_item, should_rerun) else: pass t.save_queue() if t.has_queue: if not is_running('generate_thumbnails'): run_in_background( 'generate_thumbnails', ['/usr/bin/python3', wf.workflowfile('thumbnails.py')])
def test_get_title(self): the_matrix_title_id = '10' just_watch = JustWatch() titles = just_watch.get_title(the_matrix_title_id) self.assertIsNotNone(titles) self.assertGreater(len(titles), 0)
}}, page_size=pageSize) for item in page['items']: peliculas.append(item['id']) print('Página ' + str(count) + '/' + str(totalPages)) count += 1 count = 0 while (count < len(peliculas)): try: movie = just_watch.get_title(title_id=peliculas[count]) # print(json.dumps(movie, indent=4, sort_keys=True)) if ((movie['object_type'] == 'movie') & ("offers" in movie)): name = movie['title'] + ' (' + str( movie['original_release_year']) + ')' genresId = movie['genre_ids'] i = 0 genres = [] for id in genresId: genres.append(GenreIdToGenero(id))
class Watchlist(object): # Init def __init__(self): # Libraries self.letterboxd = Letterboxd(LB_USERNAME, LB_PASSWORD) self.tmdb = TMDb(TMDB_API_KEY) self.justwatch = JustWatch(country=JUSTWATCH_COUNTRY) self.justwatch.locale = JUSTWATCH_LOCALE # Load cached watchlist films self.films = self.load() # Load def load(self): logger.info('Loading watchlist from cache') watchlist = {} try: f = open(WATCHLIST_CACHE, 'r', 'utf-8') watchlist = loads(f.read()) f.close() except: logger.exception('Could not load watchlist cache') logger.info('Loaded %d films from watchlist cache' % (len(watchlist))) return watchlist # Save def save(self): logger.info('Saving films to watchlist cache') try: with open(WATCHLIST_CACHE, 'w', 'utf-8') as f: f.write(dumps(self.films, indent=4, ensure_ascii=False)) except: logger.exception('Could not save watchlist cache') else: logger.info('Saved %d films to watchlist cache' % (len(self.films))) # Sync @json_request def sync(self): logger.info('Syncing watchlist') results = {'new': {}, 'removed': []} # Fetch Letterboxd watchlist logger.info('> Existing films: %d' % (len(self.films.keys()))) lb_watchlist = self.letterboxd.watchlist() logger.info('> Got %d films from Letterboxd' % (len(lb_watchlist.keys()))) logger.info('Updating watchlist') for slug, metadata in lb_watchlist.iteritems(): if slug in self.films: # Update self.films[slug]['ids']['letterboxd'] = metadata['id'] self.films[slug]['title'] = metadata['title'] # self.films[slug]['year'] = metadata['year'] else: # Create self.films[slug] = { 'ids': { 'letterboxd': metadata['id'] }, 'title': metadata['title'], # 'year': metadata['year'], } results['new'][slug] = self.films[slug] logger.info('> Added %s' % (slug)) # Find removed removed = [ f for f in self.films.keys() if f not in lb_watchlist.keys() ] for slug in removed: logger.info('> Removed %s' % (slug)) del self.films[slug] results['removed'] = removed # Save self.save() return results # Films @json_request def all_films(self): return self.films # Search TMDb @json_request def search_tmdb(self, slug): # Query title, year = self.parse_slug(slug) logger.info('> Searching TMDb, title=%s, year=%s' % (title, year)) tmdb = self.tmdb.search(title, year=year) results = [] for film in tmdb.get('results', []): year = film.get('release_date') year = int(year.split('-')[0]) if year else None overview = film.get('overview', '') overview = overview[0:200] + '...' if len( overview) > 200 else overview results.append({ 'id': film.get('id'), 'title': film.get('title'), 'year': year, 'overview': overview }) return results # Search JustWatch @json_request def search_justwatch(self, slug): # Query title, year = self.parse_slug(slug) logger.info('> Searching JustWatch, title=%s, year=%s' % (title, year)) justwatch = self.justwatch.search_for_item( **{ 'query': title, 'page_size': 10, 'release_year_from': (year - 1) if year else None, 'release_year_until': (year + 1) if year else None, }) results = [] for film in justwatch.get('items', []): if film.get('object_type') != 'movie': continue tmdb_id = None for scoring in film.get('scoring', []): if scoring['provider_type'] == 'tmdb:id': tmdb_id = scoring['value'] overview = film.get('short_description', '') overview = overview[0:200] + '...' if len( overview) > 200 else overview results.append({ 'id': film.get('id'), 'title': film.get('title'), 'original_title': film.get('original_title'), 'year': film.get('original_release_year'), 'overview': overview, 'tmdb_id': tmdb_id }) # Check if TMDb ID is available film_tmdb_id = self.films[slug].get('ids', {}).get('tmdb') if film_tmdb_id: logger.info( 'TMDb ID available (%s), retuning single result if match' % (film_tmdb_id)) for result in results: if result['tmdb_id'] == film_tmdb_id: return [result] return results # Update metadata @json_request def update_metadata(self, slug, tmdb_id): if slug not in self.films: logger.warning('Could not update "%s", not in watchlist' % (slug)) return None # Get metadata logger.info('Getting metadata for "%s" using TMDb id=%s' % (slug, tmdb_id)) details = self.tmdb.details(tmdb_id) if not details or details.get('status_code'): raise Exception('No metadata found for %s' % (slug)) # Parse TMDb details try: # Details year = details.get('release_date') year = int(year.split('-')[0]) if year else None credits = details.get('credits', {}) crew = credits.get('crew', []) metadata = { 'title': details.get('title'), 'original_title': details.get('original_title'), 'year': year, 'overview': details.get('overview'), 'genres': [g['name'] for g in details.get('genres', [])], 'runtime': details.get('runtime'), 'original_language': details.get('original_language'), 'spoken_languages': [l['name'] for l in details.get('spoken_languages', [])], 'directors': [p['name'] for p in crew if p['job'] == 'Director'], 'writers': [p['name'] for p in crew if p['job'] == 'Writer'], } # Images if details.get('backdrop_path') and not path.isfile( path.join(BACKDROPS_PATH, '%s.jpg' % (slug))): try: backdrop_url = TMDB_BACKDROP_URL % ( details.get('backdrop_path')) logger.info('Fetching backdrop for "%s", url=%s' % (slug, backdrop_url)) r = get(backdrop_url, stream=True) r.raise_for_status() with open(path.join(BACKDROPS_PATH, '%s.jpg' % (slug)), 'wb') as f: r.raw.decode_content = True copyfileobj(r.raw, f) except: logger.exception('Could not save backdrop image') else: logger.warning('No backdrop found for "%s"' % (slug)) except: logger.exception('TMDb parse error') raise Exception('Could not parse metadata for %s' % (slug)) # Update film logger.info('Updating metadata for "%s"' % (slug)) self.films[slug]['ids']['tmdb'] = details.get('id') self.films[slug]['ids']['imdb'] = details.get('imdb_id') self.films[slug]['metadata'] = metadata self.save() return metadata # Update offerings @json_request def update_offerings(self, slug, justwatch_id): if slug not in self.films: logger.warning('Could not update "%s", not in watchlist' % (slug)) return None # Get offerings logger.info('Getting offerings for "%s" using JustWatch id=%s' % (slug, justwatch_id)) try: providers = { p['id']: p['clear_name'] for p in self.justwatch.get_providers() } justwatch = self.justwatch.get_title(title_id=justwatch_id) print dumps(justwatch, indent=4) offers = justwatch.get('offers', []) justwatch_id = justwatch['id'] justwatch_url = justwatch.get('full_paths', {}).get('MOVIE_DETAIL_OVERVIEW') except: logger.exception( 'No offerings found for "%s" using JustWatch id=%s' % (slug, justwatch_id)) return {} # if not offers: # logger.error('No offerings found for "%s" using JustWatch id=%s' % (slug, justwatch_id)) # return {} # Parse JustWatch data try: # Offerings offerings = {} for offer in offers: if offer.get('provider_id') not in offerings: offerings[offer.get('provider_id')] = { 'name': providers.get(offer.get('provider_id')), 'offers': [], 'offer_types': [], } offerings[offer.get('provider_id')]['offers'].append({ 'date_created': offer.get('date_created'), 'monetization_type': offer.get('monetization_type'), 'presentation_type': offer.get('presentation_type'), # 'provider_id': offer.get('provider_id'), 'urls': offer.get('urls', {}), 'price': offer.get('retail_price'), 'currency': offer.get('currency'), }) if offer.get('monetization_type') not in offerings[offer.get( 'provider_id')]['offer_types']: offerings[offer.get('provider_id')]['offer_types'].append( offer.get('monetization_type')) # Scoring tomato_id = None scoring = {} average_score = None scores = [] for score in justwatch.get('scoring', []): if ':id' not in score['provider_type']: key = score['provider_type'].replace(':', '_') scoring[key] = score['value'] if key == 'imdb_score': scores.append(float(score['value'])) if key == 'tmdb_score': scores.append(float(score['value'])) if key == 'tomato_score': scores.append((float(score['value']) / 10)) if key == 'metacritic_score': scores.append((float(score['value']) / 10)) if score['provider_type'] == 'tomato:id': tomato_id = score['value'] # Calculate average if len(scores) > 0: average_score = (float(sum(scores)) / len(scores)) average_score = round(average_score, 2) except: logger.exception('Could not parse metadata for %s' % (slug)) return {} # Update film logger.info('Updating offerings for "%s"' % (slug)) self.films[slug]['ids']['justwatch'] = justwatch_id self.films[slug]['ids']['tomato'] = tomato_id self.films[slug]['offerings'] = offerings self.films[slug]['offerings_updated'] = time() self.films[slug]['offerings_updated_str'] = datetime.now().strftime( '%Y-%m-%d') self.films[slug]['justwatch_url'] = justwatch_url self.films[slug]['scoring'] = scoring self.films[slug]['scoring']['average'] = average_score self.save() return offerings # Offerings update @json_request def offerings_update(self): # Queue logger.info('Find films queued for offerings update') logger.info('> Max requests: %d' % (JUSTWATCH_MAX_REQUESTS)) logger.info('> Check age: %d' % (JUSTWATCH_CHECK_AGE)) queue = {} for slug, film in self.films.iteritems(): if not film.get('offerings_updated') or not film.get( 'ids', []).get('justwatch'): continue update_age = (time() - film.get('offerings_updated')) if update_age < JUSTWATCH_CHECK_AGE: continue queue[slug] = { 'slug': slug, 'justwatch_id': film.get('ids', []).get('justwatch'), 'last_update': film.get('offerings_updated'), 'update_age': update_age, 'result': {}, } if (len(queue) == JUSTWATCH_MAX_REQUESTS): break # Update for slug, film in queue.iteritems(): # Update offerings # offers = [] result = self.update_offerings( slug, film.get('justwatch_id')).get('result') # for provider_id, provider in result.iteritems(): # offers.append({ # 'name': provider.get('name'), # 'offers': provider.get('offer_types'), # }) queue[slug]['result'] = result return queue # Genres @json_request def genres(self): genres = {} for slug, film in self.films.iteritems(): if film.get('metadata', {}).get('genres'): for genre in film.get('metadata', {}).get('genres'): if genre not in genres: genres[genre] = 0 genres[genre] += 1 return genres # Providers @json_request def providers(self): return { p['id']: p['clear_name'] for p in self.justwatch.get_providers() } # Parse slug def parse_slug(self, slug): year = search(r'\-([0-9]{4}$|[0-9]{4}\-[0-9]$)', slug) year_split = year.group() if year else '---' year = year.groups(1)[0].split('-')[0] if year else None year = int(year) if year and int(year) <= (datetime.today().year + 2) else None title = slug.split(year_split)[0].replace('-', ' ') return title, year # Property: Size @property def size(self): return len(self.films)