Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions bazarr/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from .movies import api_ns_list_movies
from .providers import api_ns_list_providers
from .series import api_ns_list_series
from .sports import api_ns_list_sports
from .subtitles import api_ns_list_subtitles
from .system import api_ns_list_system
from .webhooks import api_ns_list_webhooks
Expand All @@ -25,6 +26,7 @@
api_ns_list_movies,
api_ns_list_providers,
api_ns_list_series,
api_ns_list_sports,
api_ns_list_subtitles,
api_ns_list_system,
api_ns_list_webhooks,
Expand Down
23 changes: 22 additions & 1 deletion bazarr/api/badges/badges.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
from functools import reduce
from flask_restx import Resource, Namespace, fields, marshal

from app.database import get_exclusion_clause, TableEpisodes, TableShows, TableMovies, database, select
from app.database import (get_exclusion_clause, TableEpisodes, TableShows, TableMovies, TableSportsEvents,
TableSportsLeagues, database, select)
from app.config import settings

from app.get_providers import get_throttled_providers
from app.signalr_client import sonarr_signalr_client, radarr_signalr_client
from sportarr.sse_client import sportarr_sse_client
from app.announcements import get_all_announcements
from utilities.health import get_health_issues

Expand All @@ -25,10 +27,12 @@ class Badges(Resource):
get_model = api_ns_badges.model('BadgesGet', {
'episodes': fields.Integer(),
'movies': fields.Integer(),
'sports': fields.Integer(),
'providers': fields.Integer(),
'status': fields.Integer(),
'sonarr_signalr': fields.String(),
'radarr_signalr': fields.String(),
'sportarr_sse': fields.String(),
'announcements': fields.Integer(),
})

Expand Down Expand Up @@ -62,6 +66,21 @@ def get(self):
for movie in missing_movies:
missing_movies_count += len(ast.literal_eval(movie.missing_subtitles))

# Counted per playable file, because a sports row is one part. An event
# with two parts wants subtitles for both.
sports_conditions = [(TableSportsEvents.missing_subtitles.is_not(None)),
(TableSportsEvents.missing_subtitles != '[]')]
sports_conditions += get_exclusion_clause('sports')
missing_sports = database.execute(
select(TableSportsEvents.missing_subtitles)
.select_from(TableSportsEvents)
.join(TableSportsLeagues)
.where(reduce(operator.and_, sports_conditions))) \
.all()
missing_sports_count = 0
for event in missing_sports:
missing_sports_count += len(ast.literal_eval(event.missing_subtitles))

throttled_providers = len(get_throttled_providers())

health_issues = len(get_health_issues())
Expand All @@ -71,10 +90,12 @@ def get(self):
result = {
"episodes": missing_episodes_count,
"movies": missing_movies_count,
"sports": missing_sports_count,
"providers": throttled_providers,
"status": health_issues,
'sonarr_signalr': live_str if sonarr_signalr_client.connected else "DOWN",
'radarr_signalr': live_str if radarr_signalr_client.connected else "DOWN",
'sportarr_sse': live_str if sportarr_sse_client.connected else "DOWN",
'announcements': len(get_all_announcements()),
}
return marshal(result, self.get_model)
18 changes: 18 additions & 0 deletions bazarr/api/sports/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# coding=utf-8

from .leagues import api_ns_sports_leagues
from .events import api_ns_sports_events
from .history import api_ns_sports_history
from .tags import api_ns_sports_tags
from .wanted import api_ns_sports_wanted
from .blacklist import api_ns_sports_blacklist


api_ns_list_sports = [
api_ns_sports_blacklist,
api_ns_sports_events,
api_ns_sports_history,
api_ns_sports_leagues,
api_ns_sports_tags,
api_ns_sports_wanted,
]
152 changes: 152 additions & 0 deletions bazarr/api/sports/blacklist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# coding=utf-8

import pretty

from flask_restx import Resource, Namespace, reqparse, fields, marshal

from app.database import TableSportsEvents, TableSportsLeagues, TableBlacklistSports, database, select
from subtitles.tools.delete import delete_subtitles
from sportarr.blacklist import blacklist_log_sports, blacklist_delete_all_sports, blacklist_delete_sports
from utilities.path_mappings import path_mappings
from subtitles.mass_download import sports_event_download_subtitles
from app.event_handler import event_stream
from api.swaggerui import subtitles_language_model

from ..utils import authenticate, postprocess

api_ns_sports_blacklist = Namespace('Sports Blacklist', description='List, add or remove subtitles to or from '
'sports events blacklist')


@api_ns_sports_blacklist.route('sports/blacklist')
class SportsBlacklist(Resource):
get_request_parser = reqparse.RequestParser()
get_request_parser.add_argument('start', type=int, required=False, default=0, help='Paging start integer')
get_request_parser.add_argument('length', type=int, required=False, default=-1, help='Paging length integer')

get_language_model = api_ns_sports_blacklist.model('subtitles_language_model', subtitles_language_model)

get_response_model = api_ns_sports_blacklist.model('SportsBlacklistGetResponse', {
'leagueTitle': fields.String(),
'eventTitle': fields.String(),
'partName': fields.String(),
'sportarrLeagueId': fields.Integer(),
'sportsEventId': fields.Integer(),
'provider': fields.String(),
'subs_id': fields.String(),
'language': fields.Nested(get_language_model),
'timestamp': fields.String(),
'parsed_timestamp': fields.String(),
})

@authenticate
@api_ns_sports_blacklist.response(401, 'Not Authenticated')
@api_ns_sports_blacklist.doc(parser=get_request_parser)
def get(self):
"""List blacklisted sports events subtitles"""
args = self.get_request_parser.parse_args()
start = args.get('start')
length = args.get('length')

stmt = select(TableSportsLeagues.title.label('leagueTitle'),
TableSportsEvents.title.label('eventTitle'),
TableSportsEvents.partName,
TableBlacklistSports.sportarr_league_id.label('sportarrLeagueId'),
TableBlacklistSports.sports_event_id.label('sportsEventId'),
TableBlacklistSports.provider,
TableBlacklistSports.subs_id,
TableBlacklistSports.language,
TableBlacklistSports.timestamp) \
.select_from(TableBlacklistSports) \
.join(TableSportsLeagues,
onclause=TableBlacklistSports.sportarr_league_id == TableSportsLeagues.sportarrLeagueId) \
.join(TableSportsEvents, onclause=TableBlacklistSports.sports_event_id == TableSportsEvents.id) \
.order_by(TableBlacklistSports.timestamp.desc())
if length > 0:
stmt = stmt.limit(length).offset(start)

return marshal([postprocess({
'leagueTitle': x.leagueTitle,
'eventTitle': x.eventTitle,
'partName': x.partName,
'sportarrLeagueId': x.sportarrLeagueId,
'sportsEventId': x.sportsEventId,
'provider': x.provider,
'subs_id': x.subs_id,
'language': x.language,
'timestamp': pretty.date(x.timestamp),
'parsed_timestamp': x.timestamp.strftime('%x %X')
}) for x in database.execute(stmt).all()], self.get_response_model, envelope='data')

post_request_parser = reqparse.RequestParser()
post_request_parser.add_argument('leagueid', type=int, required=True, help='League ID')
post_request_parser.add_argument('eventid', type=int, required=True, help='Sports event ID')
post_request_parser.add_argument('provider', type=str, required=True, help='Provider name')
post_request_parser.add_argument('subs_id', type=str, required=True, help='Subtitles ID')
post_request_parser.add_argument('language', type=str, required=True, help='Subtitles language')
post_request_parser.add_argument('subtitles_path', type=str, required=True, help='Subtitles file path')

@authenticate
@api_ns_sports_blacklist.doc(parser=post_request_parser)
@api_ns_sports_blacklist.response(200, 'Success')
@api_ns_sports_blacklist.response(401, 'Not Authenticated')
@api_ns_sports_blacklist.response(404, 'Sports event not found')
@api_ns_sports_blacklist.response(500, 'Subtitles file not found or permission issue.')
def post(self):
"""Add a sports events subtitles to blacklist"""
args = self.post_request_parser.parse_args()
sportarr_league_id = args.get('leagueid')
sports_event_id = args.get('eventid')
provider = args.get('provider')
subs_id = args.get('subs_id')
language = args.get('language')

eventInfo = database.execute(
select(TableSportsEvents.path)
.where(TableSportsEvents.id == sports_event_id)) \
.first()

if not eventInfo:
return 'Sports event not found', 404

media_path = eventInfo.path
subtitles_path = args.get('subtitles_path')

blacklist_log_sports(sportarr_league_id=sportarr_league_id,
sports_event_id=sports_event_id,
provider=provider,
subs_id=subs_id,
language=language)
if delete_subtitles(media_type='sports',
language=language,
forced=False,
hi=False,
media_path=path_mappings.path_replace_sports(media_path),
subtitles_path=subtitles_path,
sportarr_league_id=sportarr_league_id,
sports_event_id=sports_event_id):
sports_event_download_subtitles(no=sports_event_id)
event_stream(type='sports-event-history')
return '', 200
else:
return 'Subtitles file not found or permission issue.', 500

delete_request_parser = reqparse.RequestParser()
delete_request_parser.add_argument('all', type=str, required=False, help='Empty sports events subtitles blacklist')
delete_request_parser.add_argument('provider', type=str, required=False, help='Provider name')
delete_request_parser.add_argument('subs_id', type=str, required=False, help='Subtitles ID')

@authenticate
@api_ns_sports_blacklist.doc(parser=delete_request_parser)
@api_ns_sports_blacklist.response(204, 'Success')
@api_ns_sports_blacklist.response(401, 'Not Authenticated')
def delete(self):
"""Delete a sports events subtitles from blacklist"""
args = self.delete_request_parser.parse_args()
if args.get("all") == "true":
blacklist_delete_all_sports()
else:
provider = args.get('provider')
subs_id = args.get('subs_id')
blacklist_delete_sports(provider=provider, subs_id=subs_id)
return '', 204
101 changes: 101 additions & 0 deletions bazarr/api/sports/events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# coding=utf-8

from flask_restx import Resource, Namespace, reqparse, fields, marshal

from app.database import TableSportsEvents, database, select
from api.swaggerui import subtitles_model, subtitles_language_model, audio_language_model

from ..utils import authenticate, postprocess

api_ns_sports_events = Namespace('Sports Events', description='List sports events metadata for specific leagues or '
'events.')


@api_ns_sports_events.route('sports/events')
class SportsEvents(Resource):
get_request_parser = reqparse.RequestParser()
get_request_parser.add_argument('leagueid[]', type=int, action='append', required=False, default=[],
help='League IDs to list events for')
get_request_parser.add_argument('eventid[]', type=int, action='append', required=False, default=[],
help='Sports events ID to list')

get_subtitles_model = api_ns_sports_events.model('subtitles_model', subtitles_model)
get_subtitles_language_model = api_ns_sports_events.model('subtitles_language_model', subtitles_language_model)
get_audio_language_model = api_ns_sports_events.model('audio_language_model', audio_language_model)

get_response_model = api_ns_sports_events.model('SportsEventGetResponse', {
'audio_language': fields.Nested(get_audio_language_model),
'episode': fields.Integer(),
'missing_subtitles': fields.Nested(get_subtitles_language_model),
'monitored': fields.Boolean(),
'path': fields.String(),
'season': fields.Integer(),
'sportsEventId': fields.Integer(),
'sportarrLeagueId': fields.Integer(),
'subtitles': fields.Nested(get_subtitles_model),
'title': fields.String(),
'partName': fields.String(),
'partNumber': fields.Integer(),
'sceneName': fields.String(),
'broadcastDate': fields.String(),
})

@authenticate
@api_ns_sports_events.doc(parser=get_request_parser)
@api_ns_sports_events.response(200, 'Success')
@api_ns_sports_events.response(401, 'Not Authenticated')
@api_ns_sports_events.response(404, 'League or Sports event ID not provided')
def get(self):
"""List sports events metadata for specific leagues or events"""
args = self.get_request_parser.parse_args()
leagueId = args.get('leagueid[]')
eventId = args.get('eventid[]')

stmt = select(
TableSportsEvents.audio_language,
TableSportsEvents.episode,
TableSportsEvents.missing_subtitles,
TableSportsEvents.monitored,
TableSportsEvents.path,
TableSportsEvents.season,
TableSportsEvents.id,
TableSportsEvents.sportarrLeagueId,
TableSportsEvents.title,
TableSportsEvents.partName,
TableSportsEvents.partNumber,
TableSportsEvents.sceneName,
TableSportsEvents.broadcastDate,
)

if len(eventId) > 0:
stmt_query = database.execute(
stmt
.where(TableSportsEvents.id.in_(eventId)))\
.all()
elif len(leagueId) > 0:
# An event can hold more than one file, so order by the part as well
# to keep the prelims above the main card.
stmt_query = database.execute(
stmt
.where(TableSportsEvents.sportarrLeagueId.in_(leagueId))
.order_by(TableSportsEvents.season.desc(), TableSportsEvents.episode.desc(),
TableSportsEvents.partNumber.asc()))\
.all()
else:
return "League or Sports event ID not provided", 404

return marshal([postprocess({
'audio_language': x.audio_language,
'episode': x.episode,
'missing_subtitles': x.missing_subtitles,
'monitored': x.monitored,
'path': x.path,
'season': x.season,
'sportsEventId': x.id,
'sportarrLeagueId': x.sportarrLeagueId,
'title': x.title,
'partName': x.partName,
'partNumber': x.partNumber,
'sceneName': x.sceneName,
'broadcastDate': x.broadcastDate,
}) for x in stmt_query], self.get_response_model, envelope='data')
Loading