def test_movie(self): # populate some required data nefarious_settings = NefariousSettings() # required tmdb config data for saving models nefarious_settings.tmdb_configuration = { 'images': { 'secure_base_url': 'https://image.tmdb.org/t/p/', }, } tmdb_client = get_tmdb_client(nefarious_settings) # use the first super user account to assign media user = User.objects.create_superuser('test', '*****@*****.**', 'test') # import importer = MovieImporter( nefarious_settings=nefarious_settings, root_path='/test-download', tmdb_client=tmdb_client, user=user, ) for test_result in self.movie_tests: # prepend '/movie' to the test path test_path = os.path.join('/movie', test_result[0]) import_result = importer.ingest_path(test_path) if test_result[1] is False or import_result is False: self.assertEqual( test_result[1], import_result, '{} != {}'.format(test_result[1], import_result)) else: watch_movie = WatchMovie(name=test_result[1]) self.assertTrue( watch_movie.name == import_result.name, '{} != {}'.format(watch_movie.name, import_result.name))
def __init__(self, media_type: str, query: str, search_seed_only: bool = False): assert media_type in [MEDIA_TYPE_TV, MEDIA_TYPE_MOVIE] self.search_seed_only = search_seed_only self.nefarious_settings = NefariousSettings.get() params = { 'apikey': self.nefarious_settings.jackett_token, 'Query': query, 'Category[]': self._categories(media_type), 'Tracker[]': self._trackers(), } res = requests.get(get_jackett_search_url(self.nefarious_settings), params, timeout=90) if res.ok: response = res.json() self.results = response['Results'] else: self.ok = False self.error_content = res.content
def get(self, request, media_type, media_id): nefarious_settings = NefariousSettings.get() tmdb = get_tmdb_client(nefarious_settings) params = { 'language': nefarious_settings.language, } if media_type == MEDIA_TYPE_MOVIE: movie = tmdb.Movies(media_id) response = movie.info(**params) else: tv = tmdb.TV(media_id) response = tv.info(**params) # omit season "0" -- special episodes response['seasons'] = [ season for season in response['seasons'] if season['season_number'] > 0 ] for season in response['seasons']: seasons_request = tmdb.TV_Seasons(response['id'], season['season_number']) seasons = seasons_request.info(**params) season['episodes'] = seasons['episodes'] return Response(response)
def watch_tv_show_season_task(watch_tv_season_id: int): processor = WatchTVSeasonProcessor(watch_media_id=watch_tv_season_id) success = processor.fetch() # delete watch_tv_season and fallback to individual episodes if not success: watch_tv_season = get_object_or_404(WatchTVSeason, pk=watch_tv_season_id) logging.info('Failed fetching season {} - falling back to individual episodes'.format(watch_tv_season)) nefarious_settings = NefariousSettings.get() tmdb = get_tmdb_client(nefarious_settings) season_request = tmdb.TV_Seasons(watch_tv_season.watch_tv_show.tmdb_show_id, watch_tv_season.season_number) season = season_request.info() # save individual episode watches watch_tv_episodes_tasks = [] for episode in season['episodes']: watch_tv_episode, was_created = WatchTVEpisode.objects.get_or_create( user=watch_tv_season.user, watch_tv_show=watch_tv_season.watch_tv_show, tmdb_episode_id=episode['id'], season_number=watch_tv_season.season_number, episode_number=episode['episode_number'], ) # build list of tasks to execute watch_tv_episodes_tasks.append(watch_tv_episode_task.si(watch_tv_episode.id)) # remove the "watch season" now that we've requested to fetch all individual episodes watch_tv_season.delete() # execute tasks sequentially chain(*watch_tv_episodes_tasks)()
def get(self, request): media_type = request.query_params.get('media_type', MEDIA_TYPE_TV) assert media_type in [MEDIA_TYPE_TV, MEDIA_TYPE_MOVIE] if 'tmdb_media_id' not in request.query_params: raise ValidationError({'tmdb_media_id': ['required parameter']}) nefarious_settings = NefariousSettings.get() params = { 'language': nefarious_settings.language, } # prepare query tmdb = get_tmdb_client(nefarious_settings) tmdb_media_id = request.query_params.get('tmdb_media_id') # search for media if media_type == MEDIA_TYPE_MOVIE: similar_results = tmdb.Movies(id=tmdb_media_id).recommendations( **params) else: similar_results = tmdb.TV(id=tmdb_media_id).recommendations( **params) return Response(similar_results)
def import_library_task(media_type: str, user_id: int): user = get_object_or_404(User, pk=user_id) nefarious_settings = NefariousSettings.get() tmdb_client = get_tmdb_client(nefarious_settings=nefarious_settings) if media_type == 'movie': download_path = os.path.join( settings.INTERNAL_DOWNLOAD_PATH, nefarious_settings.transmission_movie_download_dir) importer = MovieImporter( nefarious_settings=nefarious_settings, download_path=download_path, tmdb_client=tmdb_client, user=user, ) else: download_path = os.path.join( settings.INTERNAL_DOWNLOAD_PATH, nefarious_settings.transmission_tv_download_dir) importer = TVImporter( nefarious_settings=nefarious_settings, download_path=download_path, tmdb_client=tmdb_client, user=user, ) importer.ingest_root(download_path)
def get(self, request): media_type = request.query_params.get('media_type', SEARCH_MEDIA_TYPE_TV) assert media_type in [SEARCH_MEDIA_TYPE_TV, SEARCH_MEDIA_TYPE_MOVIE] nefarious_settings = NefariousSettings.get() # prepare query tmdb = get_tmdb_client(nefarious_settings) page = request.query_params.get('page', 1) query = request.query_params.get('q') params = { 'query': query, 'page': page, 'language': nefarious_settings.language, } # search for media search = tmdb.Search() if media_type == SEARCH_MEDIA_TYPE_MOVIE: results = search.movie(**params) else: results = search.tv(**params) return Response(results)
def import_library_task(media_type: str, user_id: int, sub_path: str = None): user = get_object_or_404(User, pk=user_id) nefarious_settings = NefariousSettings.get() tmdb_client = get_tmdb_client(nefarious_settings=nefarious_settings) if media_type == 'movie': root_path = os.path.join( settings.INTERNAL_DOWNLOAD_PATH, nefarious_settings.transmission_movie_download_dir) importer = MovieImporter( nefarious_settings=nefarious_settings, root_path=root_path, tmdb_client=tmdb_client, user=user, ) else: root_path = os.path.join( settings.INTERNAL_DOWNLOAD_PATH, nefarious_settings.transmission_tv_download_dir) importer = TVImporter( nefarious_settings=nefarious_settings, root_path=root_path, tmdb_client=tmdb_client, user=user, ) # prefer supplied sub path and fallback to root path path = sub_path or root_path logger_background.info('Importing {} library at {}'.format( media_type, path)) # ingest importer.ingest_root(path)
def blacklist_auto_retry(self, request, pk): watch_media = self.get_object() nefarious_settings = NefariousSettings.get() # add to blacklist logger_foreground.info('Blacklisting {}'.format( watch_media.transmission_torrent_hash)) TorrentBlacklist.objects.get_or_create( hash=watch_media.transmission_torrent_hash, defaults=dict(name=str(watch_media), )) # unset previous details del_transmission_torrent_hash = watch_media.transmission_torrent_hash watch_media.transmission_torrent_hash = None watch_media.collected = False watch_media.collected_date = None watch_media.save() # re-queue search self._watch_media_task(watch_media_id=watch_media.id) # remove torrent and delete data logger_foreground.info('Removing blacklisted torrent hash: {}'.format( del_transmission_torrent_hash)) transmission_client = get_transmission_client( nefarious_settings=nefarious_settings) transmission_client.remove_torrent([del_transmission_torrent_hash], delete_data=True) return Response(self.serializer_class(watch_media).data)
def completed_media_task(): nefarious_settings = NefariousSettings.get() transmission_client = get_transmission_client(nefarious_settings) incomplete_kwargs = dict(collected=False, transmission_torrent_hash__isnull=False) movies = WatchMovie.objects.filter(**incomplete_kwargs) tv_seasons = WatchTVSeason.objects.filter(**incomplete_kwargs) tv_episodes = WatchTVEpisode.objects.filter(**incomplete_kwargs) incomplete_media = list(movies) + list(tv_episodes) + list(tv_seasons) for media in incomplete_media: try: torrent = transmission_client.get_torrent( media.transmission_torrent_hash) if torrent.progress == 100: logging.info('Media completed: {}'.format(media)) media.collected = True media.collected_date = datetime.utcnow() media.save() except KeyError: logging.info( "Media's torrent no longer present, removing reference: {}". format(media)) media.transmission_torrent_hash = None media.save()
def __init__(self, watch_media_id: int): self.nefarious_settings = NefariousSettings.get() self.tmdb_client = get_tmdb_client(self.nefarious_settings) self.transmission_client = get_transmission_client( self.nefarious_settings) self.watch_media = self._get_watch_media(watch_media_id) self.tmdb_media = self._get_tmdb_media()
def completed_media_task(): nefarious_settings = NefariousSettings.get() transmission_client = get_transmission_client(nefarious_settings) incomplete_kwargs = dict(collected=False, transmission_torrent_hash__isnull=False) movies = WatchMovie.objects.filter(**incomplete_kwargs) tv_seasons = WatchTVSeason.objects.filter(**incomplete_kwargs) tv_episodes = WatchTVEpisode.objects.filter(**incomplete_kwargs) incomplete_media = list(movies) + list(tv_episodes) + list(tv_seasons) for media in incomplete_media: try: torrent = transmission_client.get_torrent(media.transmission_torrent_hash) except KeyError: # media's torrent reference no longer exists so remove the reference logging.info("Media's torrent no longer present, removing reference: {}".format(media)) media.transmission_torrent_hash = None media.save() else: # download is complete if torrent.progress == 100: # flag media as completed logging.info('Media completed: {}'.format(media)) media.collected = True media.collected_date = datetime.utcnow() media.save() # special handling for tv seasons if isinstance(media, WatchTVSeason): # mark season request complete for season_request in WatchTVSeasonRequest.objects.filter(watch_tv_show=media.watch_tv_show, season_number=media.season_number): season_request.collected = True season_request.save() # delete any individual episodes now that we have the whole season for episode in WatchTVEpisode.objects.filter(watch_tv_show=media.watch_tv_show, season_number=media.season_number): episode.delete() # rename the torrent file/path renamed_torrent_name = get_renamed_torrent(torrent, media) logging.info('Renaming torrent file from "{}" to "{}"'.format(torrent.name, renamed_torrent_name)) transmission_client.rename_torrent_path(torrent.id, torrent.name, renamed_torrent_name) # move data from staging path to actual complete path dir_name = ( nefarious_settings.transmission_movie_download_dir if isinstance(media, WatchMovie) else nefarious_settings.transmission_tv_download_dir ) transmission_session = transmission_client.session_stats() move_to_path = os.path.join( transmission_session.download_dir, dir_name.lstrip('/'), ) logging.info('Moving torrent data to "{}"'.format(move_to_path)) torrent.move_data(move_to_path)
def perform_destroy(self, instance: WatchMediaBase): # delete transmission result, including data, if it still exists nefarious_settings = NefariousSettings.get() transmission_client = get_transmission_client(nefarious_settings) transmission_client.remove_torrent( [instance.transmission_torrent_hash], delete_data=True) # fails silently super().perform_destroy(instance)
def test_tv(self): # populate some required data nefarious_settings = NefariousSettings() # required tmdb config data for saving models nefarious_settings.tmdb_configuration = { 'images': { 'secure_base_url': 'https://image.tmdb.org/t/p/', }, } tmdb_client = get_tmdb_client(nefarious_settings) # use the first super user account to assign media user = User.objects.create_superuser('test', '*****@*****.**', 'test') # import importer = TVImporter( nefarious_settings=nefarious_settings, download_path='/test-download', tmdb_client=tmdb_client, user=user, ) for test_result in self.tv_tests: # prepend '/tv' to the test path test_path = os.path.join('/tv', test_result[0]) import_result = importer.ingest_path(test_path) if test_result[1] is False or import_result is False: self.assertEqual( test_result[1], import_result, '{} != {}'.format(test_result[1], import_result)) else: show = WatchTVShow(name=test_result[1]) episode = WatchTVEpisode(watch_tv_show=show, season_number=test_result[2], episode_number=test_result[3]) self.assertTrue( episode.watch_tv_show.name == import_result.watch_tv_show.name, '{} != {}'.format(episode.watch_tv_show.name, import_result.watch_tv_show.name)) self.assertTrue( episode.season_number == import_result.season_number, '{} != {}'.format(episode.season_number, import_result.season_number)) self.assertTrue( episode.episode_number == import_result.episode_number, '{} != {}'.format(episode.episode_number, import_result.episode_number))
def wanted_tv_season_task(): tasks = [] nefarious_settings = NefariousSettings.get() tmdb = get_tmdb_client(nefarious_settings) # # re-check for requested tv seasons that have had new episodes released from TMDB (which was stale previously) # for tv_season_request in WatchTVSeasonRequest.objects.filter( collected=False): season_request = tmdb.TV_Seasons( tv_season_request.watch_tv_show.tmdb_show_id, tv_season_request.season_number) season = season_request.info() now = datetime.utcnow() last_air_date = parse_date(season.get('air_date') or '') # season air date # otherwise add any new episodes to our watch list for episode in season['episodes']: episode_air_date = parse_date(episode.get('air_date') or '') # if episode air date exists, use as last air date if episode_air_date: last_air_date = episode_air_date if not last_air_date or episode_air_date > last_air_date else last_air_date watch_tv_episode, was_created = WatchTVEpisode.objects.get_or_create( watch_tv_show=tv_season_request.watch_tv_show, season_number=tv_season_request.season_number, episode_number=episode['episode_number'], defaults=dict( # add non-unique constraint fields for the default values tmdb_episode_id=episode['id'], user=tv_season_request.user, release_date=episode_air_date, )) if was_created: logging.info('adding newly found episode {} for {}'.format( episode['episode_number'], tv_season_request)) # add episode to task queue tasks.append(watch_tv_episode_task.si(watch_tv_episode.id)) # assume there's no new episodes for anything that's aired this long ago days_since_aired = (now.date() - last_air_date).days if last_air_date else 0 if days_since_aired > 30: logging.warning('completing old tv season request {}'.format( tv_season_request)) tv_season_request.collected = True tv_season_request.save() # execute tasks sequentially chain(*tasks)()
def create(self, validated_data): # save tmdb configuration settings on creation nefarious_settings = NefariousSettings(**validated_data) tmdb_client = get_tmdb_client(nefarious_settings) configuration = tmdb_client.Configuration() validated_data['tmdb_configuration'] = configuration.info() return super().create(validated_data)
def auto_watch_new_seasons_task(): """ look for newly aired seasons that the user wants to automatically watch """ nefarious_settings = NefariousSettings.get() tmdb_client = get_tmdb_client(nefarious_settings) # cycle through every show that has auto-watch enabled for watch_show in WatchTVShow.objects.filter(auto_watch=True): tmdb_show = tmdb_client.TV(watch_show.tmdb_show_id) show_data = tmdb_show.info() added_season = False # find any season with a newer air date than the "auto watch" and queue it up for season in show_data['seasons']: air_date = parse_date(season['air_date'] or '') # air date is newer than our auto watch date if air_date and watch_show.auto_watch_date_updated and air_date >= watch_show.auto_watch_date_updated: # season & request params create_params = dict(watch_tv_show=watch_show, season_number=season['season_number'], defaults=dict( user=watch_show.user, release_date=air_date, )) # create a season request instance to keep up with slowly-released episodes WatchTVSeasonRequest.objects.get_or_create(**create_params) # also save a watch tv season instance to try and download the whole season immediately watch_tv_season, was_season_created = WatchTVSeason.objects.get_or_create( **create_params) # season was created if was_season_created: added_season = True logging.info( 'Automatically watching newly aired season {}'.format( watch_tv_season)) # send a websocket message for this new season media_type, data = websocket.get_media_type_and_serialized_watch_media( watch_tv_season) send_websocket_message_task.delay(websocket.ACTION_UPDATED, media_type, data) # create a task to download the whole season (fallback to individual episodes if it fails) watch_tv_show_season_task.delay(watch_tv_season.id) # new season added to show if added_season: # update auto watch date requested watch_show.auto_watch_date_updated = datetime.utcnow().date() watch_show.save()
def destroy_transmission_result(instance: WatchMediaBase): # delete transmission result, including data, if it still exists nefarious_settings = NefariousSettings.get() try: transmission_client = get_transmission_client(nefarious_settings) transmission_client.remove_torrent( [instance.transmission_torrent_hash], delete_data=True, timeout=10) except Exception as e: logging.warning(str(e)) logging.warning('could not destroy torrent in transmission')
def get(self, request): nefarious_settings = NefariousSettings.get() transmission_client = get_transmission_client(nefarious_settings) watch_movies = request.query_params.getlist('watch_movies', []) watch_tv_shows = request.query_params.getlist('watch_tv_shows', []) results = [] querysets = [] # movies if watch_movies: querysets.append(WatchMovie.objects.filter(id__in=watch_movies)) # tv shows if watch_tv_shows: querysets.append( WatchTVEpisode.objects.filter( watch_tv_show__id__in=watch_tv_shows)) querysets.append( WatchTVSeason.objects.filter( watch_tv_show__id__in=watch_tv_shows)) for qs in querysets: for media in qs: if isinstance(media, WatchTVSeason): media_serializer = WatchTVSeasonSerializer elif isinstance(media, WatchTVEpisode): media_serializer = WatchTVEpisodeSerializer else: media_serializer = WatchMovieSerializer result = { 'watchMedia': media_serializer(media).data, } if media.transmission_torrent_hash: try: torrent = transmission_client.get_torrent( media.transmission_torrent_hash) except (KeyError, ValueError ): # torrent no longer exists or was invalid pass except Exception as e: logger_foreground.error(str(e)) raise e else: result['torrent'] = TransmissionTorrentSerializer( torrent).data results.append(result) return Response(results)
def send_message(message: str): nefarious_settings = NefariousSettings.get() response = requests.post(nefarious_settings.webhook_url, json={nefarious_settings.webhook_key: message}, timeout=5) try: response.raise_for_status() except Exception as e: logging.warning('webhook error for url {} and data key "{}"'.format( nefarious_settings.webhook_url, nefarious_settings.webhook_key)) logging.exception(e)
def perform_destroy(self, instance: WatchMediaBase): # delete transmission result, including data, if it still exists nefarious_settings = NefariousSettings.get() try: transmission_client = get_transmission_client(nefarious_settings) transmission_client.remove_torrent( [instance.transmission_torrent_hash], delete_data=True) except: logging.warning( 'could not connect to transmission to delete the torrent') super().perform_destroy(instance)
def refresh_tmdb_configuration(): logging.info('Refreshing TMDB Configuration') nefarious_settings = NefariousSettings.get() tmdb_client = get_tmdb_client(nefarious_settings) configuration = tmdb_client.Configuration() nefarious_settings.tmdb_configuration = configuration.info() nefarious_settings.save() return nefarious_settings.tmdb_configuration
def get(self, request, media_type, media_id): assert media_type in [MEDIA_TYPE_TV, MEDIA_TYPE_MOVIE] nefarious_settings = NefariousSettings.get() # prepare query tmdb = get_tmdb_client(nefarious_settings) if media_type == MEDIA_TYPE_MOVIE: result = tmdb.Movies(media_id) else: result = tmdb.TV(media_id) return Response(result.videos())
def refresh_tmdb_configuration(): logger_background.info('Refreshing TMDB Configuration') nefarious_settings = NefariousSettings.get() tmdb_client = get_tmdb_client(nefarious_settings) configuration = tmdb_client.Configuration() nefarious_settings.tmdb_configuration = configuration.info() nefarious_settings.tmdb_languages = configuration.languages() nefarious_settings.save() return nefarious_settings.tmdb_configuration
def watch_tv_show_season_task(watch_tv_season_id: int): processor = WatchTVSeasonProcessor(watch_media_id=watch_tv_season_id) success = processor.fetch() watch_tv_season = get_object_or_404(WatchTVSeason, pk=watch_tv_season_id) # success so update the season request instance as "collected" if success: season_request = WatchTVSeasonRequest.objects.filter( watch_tv_show=watch_tv_season.watch_tv_show, season_number=watch_tv_season.season_number) if season_request.exists(): season_request = season_request.first() season_request.collected = True season_request.save() # failed so delete season instance and fallback to trying individual episodes else: logging.info( 'Failed fetching entire season {} - falling back to individual episodes' .format(watch_tv_season)) nefarious_settings = NefariousSettings.get() tmdb = get_tmdb_client(nefarious_settings) season_request = tmdb.TV_Seasons( watch_tv_season.watch_tv_show.tmdb_show_id, watch_tv_season.season_number) season = season_request.info() # save individual episode watches watch_tv_episodes_tasks = [] for episode in season['episodes']: watch_tv_episode, was_created = WatchTVEpisode.objects.get_or_create( tmdb_episode_id=episode['id'], # add non-unique constraint fields for the default values defaults=dict( user=watch_tv_season.user, watch_tv_show=watch_tv_season.watch_tv_show, season_number=watch_tv_season.season_number, episode_number=episode['episode_number'], release_date=parse_date(episode.get('air_date') or ''), )) # build list of tasks to execute watch_tv_episodes_tasks.append( watch_tv_episode_task.si(watch_tv_episode.id)) # remove the "watch season" now that we've requested to fetch all individual episodes watch_tv_season.delete() # execute tasks sequentially chain(*watch_tv_episodes_tasks)()
def send_message(message: str) -> bool: # apprise notifications - https://github.com/caronc/apprise nefarious_settings = NefariousSettings.get() if nefarious_settings.apprise_notification_url: apprise_instance = apprise.Apprise() apprise_instance.add(nefarious_settings.apprise_notification_url) try: return apprise_instance.notify(body=message, ) except Exception as e: logger_background.warning( 'apprise notification error for url {}'.format( nefarious_settings.apprise_notification_url)) logger_background.exception(e) return False return False
def get(self, request, media_type): assert media_type in [MEDIA_TYPE_TV, MEDIA_TYPE_MOVIE] nefarious_settings = NefariousSettings.get() # prepare query tmdb = get_tmdb_client(nefarious_settings) args = request.query_params genres = tmdb.Genres() if media_type == MEDIA_TYPE_MOVIE: results = genres.movie_list(**args) else: results = genres.tv_list(**args) return Response(results)
def get(self, request, media_type): assert media_type in [MEDIA_TYPE_TV, MEDIA_TYPE_MOVIE] nefarious_settings = NefariousSettings.get() # prepare query tmdb = get_tmdb_client(nefarious_settings) args = request.query_params discover = tmdb.Discover() if media_type == MEDIA_TYPE_MOVIE: results = discover.movie(**args) else: results = discover.tv(**args) return Response(results)
def post(self, request): nefarious_settings = NefariousSettings.get() torrent = request.data.get('torrent') media_type = request.data.get('media_type', MEDIA_TYPE_TV) if not is_magnet_url(torrent): torrent = swap_jackett_host(torrent, nefarious_settings) if not torrent: return Response({ 'success': False, 'error': 'Missing torrent link' }) try: torrent = trace_torrent_url(torrent) except Exception as e: return Response({ 'success': False, 'error': 'An unknown error occurred', 'error_detail': str(e) }) logging.info('adding torrent: {}'.format(torrent)) # add torrent transmission_client = get_transmission_client(nefarious_settings) transmission_session = transmission_client.session_stats() if media_type == MEDIA_TYPE_MOVIE: download_dir = os.path.join( transmission_session.download_dir, nefarious_settings.transmission_movie_download_dir.lstrip('/')) else: download_dir = os.path.join( transmission_session.download_dir, nefarious_settings.transmission_tv_download_dir.lstrip('/')) transmission_client.add_torrent( torrent, paused=True, download_dir=download_dir, ) return Response({'success': True})
def get(self, request): nefarious_settings = NefariousSettings.get() transmission_client = get_transmission_client(nefarious_settings) watch_movies = request.query_params.getlist('watch_movies') watch_tv_episodes = request.query_params.getlist('watch_tv_episodes') watch_tv_seasons = request.query_params.getlist('watch_tv_seasons') querysets = [] torrents = [] torrent_hashes = [] # movies if watch_movies: querysets.append(WatchMovie.objects.filter(id__in=watch_movies)) # tv episodes if watch_tv_episodes: querysets.append( WatchTVEpisode.objects.filter(id__in=watch_tv_episodes)) # tv seasons if watch_tv_seasons: querysets.append( WatchTVSeason.objects.filter(id__in=watch_tv_seasons)) for query in querysets: torrent_hashes += [ media.transmission_torrent_hash for media in query if media.transmission_torrent_hash ] for torrent_hash in torrent_hashes: try: torrent = transmission_client.get_torrent(torrent_hash) torrents.append(torrent) except (KeyError, ValueError): # torrent no longer exists or was invalid continue except Exception as e: logging.error(str(e)) raise e return Response( TransmissionTorrentSerializer(torrents, many=True).data)