Skip to content

Commit a21300b

Browse files
Merge development into master
2 parents 9fce3b1 + 5dc1d27 commit a21300b

101 files changed

Lines changed: 1536 additions & 2304 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ If you need something that is not already part of Bazarr, feel free to create a
7373
- Podnapisi
7474
- RegieLive
7575
- Sous-Titres.eu
76-
- Subdivx
7776
- subf2m.co
7877
- Subs.sab.bz
7978
- Subs4Free
@@ -87,6 +86,7 @@ If you need something that is not already part of Bazarr, feel free to create a
8786
- Subtitrari-noi.ro
8887
- subtitri.id.lv
8988
- Subtitulamos.tv
89+
- SubX
9090
- Supersubtitles
9191
- Titlovi
9292
- Titrari.ro

bazarr/api/episodes/episodes_subtitles.py

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# coding=utf-8
22

33
import os
4-
import time
54

5+
from io import BytesIO
66
from flask_restx import Resource, Namespace, reqparse
77
from subliminal_patch.core import SUBTITLE_EXTENSIONS
88
from werkzeug.datastructures import FileStorage
@@ -42,16 +42,12 @@ def patch(self):
4242
"""Download an episode subtitles"""
4343
args = self.patch_request_parser.parse_args()
4444

45-
job_id = episode_download_specific_subtitles(sonarr_series_id=args.get('seriesid'),
46-
sonarr_episode_id=args.get('episodeid'),
47-
language=args.get('language'), hi=args.get('hi').capitalize(),
48-
forced=args.get('forced').capitalize(), job_id=None)
45+
episode_download_specific_subtitles(sonarr_series_id=args.get('seriesid'),
46+
sonarr_episode_id=args.get('episodeid'),
47+
language=args.get('language'), hi=args.get('hi').capitalize(),
48+
forced=args.get('forced').capitalize(), job_id=None)
4949

50-
# Wait for the job to complete or fail
51-
while jobs_queue.get_job_status(job_id=job_id) in ['pending', 'running']:
52-
time.sleep(1)
53-
54-
return jobs_queue.get_job_returned_value(job_id=job_id)
50+
return '', 204
5551

5652
post_request_parser = reqparse.RequestParser()
5753
post_request_parser.add_argument('seriesid', type=int, required=True, help='Series ID')
@@ -73,7 +69,8 @@ def post(self):
7369
"""Upload an episode subtitles"""
7470
args = self.post_request_parser.parse_args()
7571

76-
_, ext = os.path.splitext(args.get('file').filename)
72+
uploaded_file = args.get('file')
73+
_, ext = os.path.splitext(uploaded_file.filename)
7774

7875
if not isinstance(ext, str) or ext.lower() not in SUBTITLE_EXTENSIONS:
7976
raise ValueError('A subtitle of an invalid format was uploaded.')
@@ -94,21 +91,20 @@ def post(self):
9491
if not os.path.exists(episodePath):
9592
return 'Episode file not found. Path mapping issue?', 500
9693

97-
job_id = manual_upload_subtitle(path=episodePath,
98-
language=args.get('language'),
99-
forced=True if args.get('forced') == 'true' else False,
100-
hi=True if args.get('hi') == 'true' else False,
101-
media_type='series',
102-
subtitle=args.get('file'),
103-
audio_language=episodeInfo.audio_language,
104-
sonarrSeriesId=sonarrSeriesId,
105-
sonarrEpisodeId=sonarrEpisodeId)
106-
107-
# Wait for the job to complete or fail
108-
while jobs_queue.get_job_status(job_id=job_id) in ['pending', 'running']:
109-
time.sleep(1)
110-
111-
return jobs_queue.get_job_returned_value(job_id=job_id)
94+
subtitle_content = BytesIO(uploaded_file.read())
95+
96+
manual_upload_subtitle(path=episodePath,
97+
language=args.get('language'),
98+
forced=True if args.get('forced') == 'true' else False,
99+
hi=True if args.get('hi') == 'true' else False,
100+
media_type='series',
101+
subtitle=subtitle_content,
102+
filename=uploaded_file.filename,
103+
audio_language=episodeInfo.audio_language,
104+
sonarrSeriesId=sonarrSeriesId,
105+
sonarrEpisodeId=sonarrEpisodeId)
106+
107+
return '', 204
112108

113109
delete_request_parser = reqparse.RequestParser()
114110
delete_request_parser.add_argument('seriesid', type=int, required=True, help='Series ID')

bazarr/api/episodes/history.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,6 @@ def get(self):
6262
length = args.get('length')
6363
episodeid = args.get('episodeid')
6464

65-
upgradable_episodes_not_perfect = get_upgradable_episode_subtitles()
66-
6765
blacklisted_subtitles = select(TableBlacklist.provider,
6866
TableBlacklist.subs_id) \
6967
.subquery()
@@ -131,6 +129,9 @@ def get(self):
131129
'blacklisted': bool(x.blacklisted),
132130
} for x in database.execute(stmt).all()]
133131

132+
upgradable_episodes_not_perfect = get_upgradable_episode_subtitles(history_id_list=[x['id'] for x in
133+
episode_history])
134+
134135
for item in episode_history:
135136
# is this language still desired or should we simply skip this subtitles from upgrade logic?
136137
still_desired = _language_still_desired(item['language'], item['profileId'])
@@ -139,7 +140,7 @@ def get(self):
139140

140141
# Mark upgradable and get original_id
141142
item.update({'original_id': upgradable_episodes_not_perfect.get(item['id'])})
142-
item.update({'upgradable': bool(item['original_id'])})
143+
item.update({'upgradable': item['id'] in upgradable_episodes_not_perfect.keys()})
143144

144145
# Mark not upgradable if video/subtitles file doesn't exist anymore or if language isn't desired anymore
145146
if item['upgradable']:

bazarr/api/movies/history.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ def get(self):
6161
length = args.get('length')
6262
radarrid = args.get('radarrid')
6363

64-
upgradable_movies_not_perfect = get_upgradable_movies_subtitles()
65-
6664
blacklisted_subtitles = select(TableBlacklistMovie.provider,
6765
TableBlacklistMovie.subs_id) \
6866
.subquery()
@@ -122,6 +120,9 @@ def get(self):
122120
'blacklisted': bool(x.blacklisted),
123121
} for x in database.execute(stmt).all()]
124122

123+
upgradable_movies_not_perfect = get_upgradable_movies_subtitles(history_id_list=[x['id'] for x in
124+
movie_history])
125+
125126
for item in movie_history:
126127
# is this language still desired or should we simply skip this subtitles from upgrade logic?
127128
still_desired = _language_still_desired(item['language'], item['profileId'])
@@ -130,7 +131,7 @@ def get(self):
130131

131132
# Mark upgradable and get original_id
132133
item.update({'original_id': upgradable_movies_not_perfect.get(item['id'])})
133-
item.update({'upgradable': bool(item['original_id'])})
134+
item.update({'upgradable': item['id'] in upgradable_movies_not_perfect.keys()})
134135

135136
# Mark not upgradable if video/subtitles file doesn't exist anymore or if language isn't desired anymore
136137
if item['upgradable']:

bazarr/api/movies/movies_subtitles.py

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# coding=utf-8
22

33
import os
4-
import time
54

5+
from io import BytesIO
66
from flask_restx import Resource, Namespace, reqparse
77
from subliminal_patch.core import SUBTITLE_EXTENSIONS
88
from werkzeug.datastructures import FileStorage
@@ -41,15 +41,11 @@ def patch(self):
4141
"""Download a movie subtitles"""
4242
args = self.patch_request_parser.parse_args()
4343

44-
job_id = movie_download_specific_subtitles(radarr_id=args.get('radarrid'), language=args.get('language'),
45-
hi=args.get('hi').capitalize(),
46-
forced=args.get('forced').capitalize(), job_id=None)
44+
movie_download_specific_subtitles(radarr_id=args.get('radarrid'), language=args.get('language'),
45+
hi=args.get('hi').capitalize(),
46+
forced=args.get('forced').capitalize(), job_id=None)
4747

48-
# Wait for the job to complete or fail
49-
while jobs_queue.get_job_status(job_id=job_id) in ['pending', 'running']:
50-
time.sleep(1)
51-
52-
return jobs_queue.get_job_returned_value(job_id=job_id)
48+
return '', 204
5349

5450
# POST: Upload Subtitles
5551
post_request_parser = reqparse.RequestParser()
@@ -72,7 +68,8 @@ def post(self):
7268
# TODO: Support Multiply Upload
7369
args = self.post_request_parser.parse_args()
7470

75-
_, ext = os.path.splitext(args.get('file').filename)
71+
uploaded_file = args.get('file')
72+
_, ext = os.path.splitext(uploaded_file.filename)
7673

7774
if not isinstance(ext, str) or ext.lower() not in SUBTITLE_EXTENSIONS:
7875
raise ValueError('A subtitle of an invalid format was uploaded.')
@@ -91,20 +88,19 @@ def post(self):
9188
if not os.path.exists(moviePath):
9289
return 'Movie file not found. Path mapping issue?', 500
9390

94-
job_id = manual_upload_subtitle(path=moviePath,
95-
language=args.get('language'),
96-
forced=True if args.get('forced') == 'true' else False,
97-
hi=True if args.get('hi') == 'true' else False,
98-
media_type='movie',
99-
subtitle=args.get('file'),
100-
audio_language=movieInfo.audio_language,
101-
radarrId=radarrId)
91+
subtitle_content = BytesIO(uploaded_file.read())
10292

103-
# Wait for the job to complete or fail
104-
while jobs_queue.get_job_status(job_id=job_id) in ['pending', 'running']:
105-
time.sleep(1)
93+
manual_upload_subtitle(path=moviePath,
94+
language=args.get('language'),
95+
forced=True if args.get('forced') == 'true' else False,
96+
hi=True if args.get('hi') == 'true' else False,
97+
media_type='movie',
98+
subtitle=subtitle_content,
99+
filename=uploaded_file.filename,
100+
audio_language=movieInfo.audio_language,
101+
radarrId=radarrId)
106102

107-
return jobs_queue.get_job_returned_value(job_id=job_id)
103+
return '', 204
108104

109105
# DELETE: Delete Subtitles
110106
delete_request_parser = reqparse.RequestParser()

bazarr/api/plex/oauth.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -954,7 +954,16 @@ def post(self):
954954
instance_param = quote_plus(instance_name)
955955

956956
scheme = 'https' if request.is_secure else 'http'
957-
host = request.host
957+
958+
host = settings.general.get('hostname', None)
959+
if not host:
960+
logger.error("Hostname must be configured in General Settings before creating webhooks. This prevents "
961+
"host header poisoning attacks.")
962+
return {
963+
'error': 'Hostname must be configured in General Settings before creating webhooks. '
964+
'This prevents host header poisoning attacks.'
965+
}, 400
966+
958967
if configured_base_url:
959968
webhook_url = f"{scheme}://{host}{configured_base_url}/api/webhooks/plex?apikey={apikey}&instance={instance_param}"
960969
logger.info(f"Using configured base URL for webhook: {scheme}://{host}{configured_base_url}/api/webhooks/plex (instance: {instance_name})")

bazarr/api/providers/providers_episodes.py

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# coding=utf-8
22

33
import os
4-
import time
54

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

@@ -107,17 +106,13 @@ def post(self):
107106
"""Manually download an episode subtitles"""
108107
args = self.post_request_parser.parse_args()
109108

110-
job_id = episode_manually_download_specific_subtitle(sonarr_series_id=args.get('seriesid'),
111-
sonarr_episode_id=args.get('episodeid'),
112-
hi=args.get('hi').capitalize(),
113-
forced=args.get('forced').capitalize(),
114-
use_original_format=args.get('original_format').capitalize(),
115-
selected_provider=args.get('provider'),
116-
subtitle=args.get('subtitle'),
117-
job_id=None)
109+
episode_manually_download_specific_subtitle(sonarr_series_id=args.get('seriesid'),
110+
sonarr_episode_id=args.get('episodeid'),
111+
hi=args.get('hi').capitalize(),
112+
forced=args.get('forced').capitalize(),
113+
use_original_format=args.get('original_format').capitalize(),
114+
selected_provider=args.get('provider'),
115+
subtitle=args.get('subtitle'),
116+
job_id=None)
118117

119-
# Wait for the job to complete or fail
120-
while jobs_queue.get_job_status(job_id=job_id) in ['pending', 'running']:
121-
time.sleep(1)
122-
123-
return jobs_queue.get_job_returned_value(job_id=job_id)
118+
return '', 204

bazarr/api/providers/providers_movies.py

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# coding=utf-8
22

33
import os
4-
import time
54

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

@@ -105,16 +104,12 @@ def post(self):
105104
"""Manually download a movie subtitles"""
106105
args = self.post_request_parser.parse_args()
107106

108-
job_id = movie_manually_download_specific_subtitle(radarr_id=args.get('radarrid'),
109-
hi=args.get('hi').capitalize(),
110-
forced=args.get('forced').capitalize(),
111-
use_original_format=args.get('original_format').capitalize(),
112-
selected_provider=args.get('provider'),
113-
subtitle=args.get('subtitle'),
114-
job_id=None)
107+
movie_manually_download_specific_subtitle(radarr_id=args.get('radarrid'),
108+
hi=args.get('hi').capitalize(),
109+
forced=args.get('forced').capitalize(),
110+
use_original_format=args.get('original_format').capitalize(),
111+
selected_provider=args.get('provider'),
112+
subtitle=args.get('subtitle'),
113+
job_id=None)
115114

116-
# Wait for the job to complete or fail
117-
while jobs_queue.get_job_status(job_id=job_id) in ['pending', 'running']:
118-
time.sleep(1)
119-
120-
return jobs_queue.get_job_returned_value(job_id=job_id)
115+
return '', 204

bazarr/api/subtitles/subtitles.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22

33
import os
44
import sys
5-
import gc
65

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

9-
from app.database import TableEpisodes, TableMovies, database, select
8+
from app.database import TableShows, TableEpisodes, TableMovies, database, select
109
from languages.get_languages import alpha3_from_alpha2
1110
from utilities.path_mappings import path_mappings
1211
from utilities.video_analyzer import subtitles_sync_references
@@ -18,6 +17,8 @@
1817
from subtitles.sync import sync_subtitles
1918
from app.config import settings, empty_values, get_array_from
2019
from app.event_handler import event_stream
20+
from plex.operations import plex_refresh_item
21+
2122

2223
from ..utils import authenticate
2324

@@ -122,7 +123,9 @@ def patch(self):
122123

123124
if media_type == 'episode':
124125
metadata = database.execute(
125-
select(TableEpisodes.path, TableEpisodes.sonarrSeriesId, TableEpisodes.subtitles)
126+
select(TableEpisodes.path, TableEpisodes.sonarrSeriesId, TableEpisodes.subtitles, TableEpisodes.season,
127+
TableEpisodes.episode, TableShows.imdbId)
128+
.join(TableShows)
126129
.where(TableEpisodes.sonarrEpisodeId == id)) \
127130
.first()
128131

@@ -132,7 +135,7 @@ def patch(self):
132135
video_path = path_mappings.path_replace(metadata.path)
133136
else:
134137
metadata = database.execute(
135-
select(TableMovies.path, TableMovies.subtitles)
138+
select(TableMovies.path, TableMovies.subtitles, TableMovies.imdbId)
136139
.where(TableMovies.radarrId == id))\
137140
.first()
138141

@@ -190,7 +193,6 @@ def patch(self):
190193
except OSError:
191194
return 'Unable to edit subtitles file. Check logs.', 409
192195
else:
193-
use_original_format = True if args.get('original_format') == 'true' else False
194196
try:
195197
subtitles_apply_mods(language=language, subtitle_path=subtitles_path, mods=[action],
196198
video_path=video_path)
@@ -207,10 +209,17 @@ def patch(self):
207209
store_subtitles(path_mappings.path_replace_reverse(video_path), video_path)
208210
event_stream(type='series', payload=metadata.sonarrSeriesId)
209211
event_stream(type='episode', payload=id)
212+
213+
if settings.general.use_plex and settings.plex.update_series_library:
214+
plex_refresh_item(metadata.imdbId, is_movie=False, season=metadata.season,
215+
episode=metadata.episode)
210216
else:
211217
store_subtitles_movie(path_mappings.path_replace_reverse_movie(video_path), video_path)
212218
event_stream(type='movie', payload=id)
213219

220+
if settings.general.use_plex and settings.plex.update_movie_library:
221+
plex_refresh_item(metadata.imdbId, is_movie=True)
222+
214223
return '', 204
215224

216225

bazarr/api/system/account.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class SystemAccount(Resource):
2828
def post(self):
2929
"""Login or logout from Bazarr UI when using form login"""
3030
args = self.post_request_parser.parse_args()
31-
if settings.auth.type != 'form':
31+
if settings.auth.type not in ['form', 'basic']:
3232
return 'Unknown authentication type define in config', 500
3333

3434
action = args.get('action')

0 commit comments

Comments
 (0)